Loading services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +24 −8 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; Loading Loading @@ -61,6 +62,7 @@ import android.util.Range; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; Loading @@ -71,6 +73,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; Loading @@ -96,9 +99,11 @@ public class MultipathPolicyTracker { private final Clock mClock; private final Dependencies mDeps; private final ContentResolver mResolver; private final SettingsObserver mSettingsObserver; private final ConfigChangeReceiver mConfigChangeReceiver; @VisibleForTesting final ContentObserver mSettingsObserver; private ConnectivityManager mCM; private NetworkPolicyManager mNPM; private NetworkStatsManager mStatsManager; Loading Loading @@ -288,12 +293,16 @@ public class MultipathPolicyTracker { final NetworkPolicy[] policies = mNPM.getNetworkPolicies(); for (NetworkPolicy policy : policies) { if (hasActiveCycle(policy) && policy.template.matches(identity)) { if (policy.hasCycle() && policy.template.matches(identity)) { final long cycleStart = policy.cycleIterator().next().getLower() .toInstant().toEpochMilli(); // Prefer user-defined warning, otherwise use hard limit final long policyBytes = (policy.warningBytes == LIMIT_DISABLED) ? policy.limitBytes : policy.warningBytes; final long activeWarning = getActiveWarning(policy, cycleStart); final long policyBytes = (activeWarning == WARNING_DISABLED) ? getActiveLimit(policy, cycleStart) : activeWarning; if (policyBytes != LIMIT_DISABLED) { if (policyBytes != LIMIT_DISABLED && policyBytes != WARNING_DISABLED) { final long policyBudget = getRemainingDailyBudget(policyBytes, policy.cycleIterator().next()); minQuota = Math.min(minQuota, policyBudget); Loading Loading @@ -388,9 +397,16 @@ public class MultipathPolicyTracker { } } private static boolean hasActiveCycle(NetworkPolicy policy) { return policy.hasCycle() && policy.lastLimitSnooze < policy.cycleIterator().next().getLower().toInstant().toEpochMilli(); private static long getActiveWarning(NetworkPolicy policy, long cycleStart) { return policy.lastWarningSnooze < cycleStart ? policy.warningBytes : WARNING_DISABLED; } private static long getActiveLimit(NetworkPolicy policy, long cycleStart) { return policy.lastLimitSnooze < cycleStart ? policy.limitBytes : LIMIT_DISABLED; } // Only ever updated on the handler thread. Accessed from other binder threads to retrieve Loading tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java 0 → 100644 +360 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.connectivity; import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; import android.os.Handler; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; import android.util.DataUnit; import android.util.RecurrenceRule; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.net.NetworkStatsManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Clock; import java.time.Instant; import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class MultipathPolicyTrackerTest { private static final Network TEST_NETWORK = new Network(123); private static final int POLICY_SNOOZED = -100; @Mock private Context mContext; @Mock private Resources mResources; @Mock private Handler mHandler; @Mock private MultipathPolicyTracker.Dependencies mDeps; @Mock private Clock mClock; @Mock private ConnectivityManager mCM; @Mock private NetworkPolicyManager mNPM; @Mock private NetworkStatsManager mStatsManager; @Mock private NetworkPolicyManagerInternal mNPMI; @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal; @Mock private TelephonyManager mTelephonyManager; private MockContentResolver mContentResolver; private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor; private MultipathPolicyTracker mTracker; private Clock mPreviousRecurrenceRuleClock; private boolean mRecurrenceRuleClockMocked; private <T> void mockService(String serviceName, Class<T> serviceClass, T service) { when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName); when(mContext.getSystemService(serviceName)).thenReturn(service); } @Before public void setUp() { MockitoAnnotations.initMocks(this); mPreviousRecurrenceRuleClock = RecurrenceRule.sClock; RecurrenceRule.sClock = mClock; mRecurrenceRuleClockMocked = true; mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); when(mContext.getResources()).thenReturn(mResources); when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); when(mContext.registerReceiverAsUser(mConfigChangeReceiverCaptor.capture(), any(), argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any())) .thenReturn(null); when(mDeps.getClock()).thenReturn(mClock); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); mContentResolver = Mockito.spy(new MockContentResolver(mContext)); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); Settings.Global.clearProviderForTest(); when(mContext.getContentResolver()).thenReturn(mContentResolver); mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM); mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM); mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager); mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI); LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class); LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal); mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps); } @After public void tearDown() { // Avoid setting static clock to null (which should normally not be the case) // if MockitoAnnotations.initMocks threw an exception if (mRecurrenceRuleClockMocked) { RecurrenceRule.sClock = mPreviousRecurrenceRuleClock; } mRecurrenceRuleClockMocked = false; } private void setDefaultQuotaGlobalSetting(long setting) { Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES, (int) setting); } private void testGetMultipathPreference( long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit, long defaultGlobalSetting, long defaultResSetting, boolean roaming) { // TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly. final ZonedDateTime now = ZonedDateTime.ofInstant( Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault()); final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS); when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli()); when(mClock.instant()).thenReturn(now.toInstant()); when(mClock.getZone()).thenReturn(ZoneId.systemDefault()); // Setup plan quota when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH)) .thenReturn(subscriptionQuota); // Setup user policy warning / limit if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) { final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z"); final RecurrenceRule recurrenceRule = new RecurrenceRule( ZonedDateTime.ofInstant( recurrenceStart, ZoneId.systemDefault()), null /* end */, Period.ofMonths(1)); final boolean snoozeWarning = policyWarning == POLICY_SNOOZED; final boolean snoozeLimit = policyLimit == POLICY_SNOOZED; when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] { new NetworkPolicy( NetworkTemplate.buildTemplateMobileWildcard(), recurrenceRule, snoozeWarning ? 0 : policyWarning, snoozeLimit ? 0 : policyLimit, snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, SNOOZE_NEVER, true /* metered */, false /* inferred */) }); } else { when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); } // Setup default quota in settings and resources if (defaultGlobalSetting > 0) { setDefaultQuotaGlobalSetting(defaultGlobalSetting); } when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) .thenReturn((int) defaultResSetting); when(mNetworkStatsManagerInternal.getNetworkTotalBytes( any(), eq(startOfDay.toInstant().toEpochMilli()), eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday); ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); mTracker.start(); verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any()); // Simulate callback after capability changes final NetworkCapabilities capabilities = new NetworkCapabilities() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .setNetworkSpecifier(new StringNetworkSpecifier("234")); if (!roaming) { capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); } networkCallback.getValue().onCapabilitiesChanged( TEST_NETWORK, capabilities); } @Test public void testGetMultipathPreference_SubscriptionQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */, DataUnit.MEGABYTES.toBytes(100) /* policyWarning */, LIMIT_DISABLED, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); } @Test public void testGetMultipathPreference_UserWarningQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */, LIMIT_DISABLED, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); } @Test public void testGetMultipathPreference_SnoozedWarningQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st POLICY_SNOOZED /* policyWarning */, DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); } @Test public void testGetMultipathPreference_SnoozedBothQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st POLICY_SNOOZED /* policyWarning */, POLICY_SNOOZED /* policyLimit */, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Default global setting should be used: 12 - 7 = 5 verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any()); } @Test public void testGetMultipathPreference_SettingChanged() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, WARNING_DISABLED, LIMIT_DISABLED, -1 /* defaultGlobalSetting */, DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); // Update setting setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14)); mTracker.mSettingsObserver.onChange( false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES)); // Callback must have been re-registered with new setting verify(mStatsManager, times(1)).unregisterUsageCallback(any()); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); } @Test public void testGetMultipathPreference_ResourceChanged() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, WARNING_DISABLED, LIMIT_DISABLED, -1 /* defaultGlobalSetting */, DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) .thenReturn((int) DataUnit.MEGABYTES.toBytes(16)); final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue(); assertNotNull(configChangeReceiver); configChangeReceiver.onReceive(mContext, new Intent()); // Uses the new setting (16 - 2 = 14MB) verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any()); } } Loading
services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +24 −8 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; Loading Loading @@ -61,6 +62,7 @@ import android.util.Range; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; Loading @@ -71,6 +73,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; Loading @@ -96,9 +99,11 @@ public class MultipathPolicyTracker { private final Clock mClock; private final Dependencies mDeps; private final ContentResolver mResolver; private final SettingsObserver mSettingsObserver; private final ConfigChangeReceiver mConfigChangeReceiver; @VisibleForTesting final ContentObserver mSettingsObserver; private ConnectivityManager mCM; private NetworkPolicyManager mNPM; private NetworkStatsManager mStatsManager; Loading Loading @@ -288,12 +293,16 @@ public class MultipathPolicyTracker { final NetworkPolicy[] policies = mNPM.getNetworkPolicies(); for (NetworkPolicy policy : policies) { if (hasActiveCycle(policy) && policy.template.matches(identity)) { if (policy.hasCycle() && policy.template.matches(identity)) { final long cycleStart = policy.cycleIterator().next().getLower() .toInstant().toEpochMilli(); // Prefer user-defined warning, otherwise use hard limit final long policyBytes = (policy.warningBytes == LIMIT_DISABLED) ? policy.limitBytes : policy.warningBytes; final long activeWarning = getActiveWarning(policy, cycleStart); final long policyBytes = (activeWarning == WARNING_DISABLED) ? getActiveLimit(policy, cycleStart) : activeWarning; if (policyBytes != LIMIT_DISABLED) { if (policyBytes != LIMIT_DISABLED && policyBytes != WARNING_DISABLED) { final long policyBudget = getRemainingDailyBudget(policyBytes, policy.cycleIterator().next()); minQuota = Math.min(minQuota, policyBudget); Loading Loading @@ -388,9 +397,16 @@ public class MultipathPolicyTracker { } } private static boolean hasActiveCycle(NetworkPolicy policy) { return policy.hasCycle() && policy.lastLimitSnooze < policy.cycleIterator().next().getLower().toInstant().toEpochMilli(); private static long getActiveWarning(NetworkPolicy policy, long cycleStart) { return policy.lastWarningSnooze < cycleStart ? policy.warningBytes : WARNING_DISABLED; } private static long getActiveLimit(NetworkPolicy policy, long cycleStart) { return policy.lastLimitSnooze < cycleStart ? policy.limitBytes : LIMIT_DISABLED; } // Only ever updated on the handler thread. Accessed from other binder threads to retrieve Loading
tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java 0 → 100644 +360 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.connectivity; import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.net.StringNetworkSpecifier; import android.os.Handler; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; import android.util.DataUnit; import android.util.RecurrenceRule; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.net.NetworkStatsManagerInternal; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.time.Clock; import java.time.Instant; import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @RunWith(AndroidJUnit4.class) @SmallTest public class MultipathPolicyTrackerTest { private static final Network TEST_NETWORK = new Network(123); private static final int POLICY_SNOOZED = -100; @Mock private Context mContext; @Mock private Resources mResources; @Mock private Handler mHandler; @Mock private MultipathPolicyTracker.Dependencies mDeps; @Mock private Clock mClock; @Mock private ConnectivityManager mCM; @Mock private NetworkPolicyManager mNPM; @Mock private NetworkStatsManager mStatsManager; @Mock private NetworkPolicyManagerInternal mNPMI; @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal; @Mock private TelephonyManager mTelephonyManager; private MockContentResolver mContentResolver; private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor; private MultipathPolicyTracker mTracker; private Clock mPreviousRecurrenceRuleClock; private boolean mRecurrenceRuleClockMocked; private <T> void mockService(String serviceName, Class<T> serviceClass, T service) { when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName); when(mContext.getSystemService(serviceName)).thenReturn(service); } @Before public void setUp() { MockitoAnnotations.initMocks(this); mPreviousRecurrenceRuleClock = RecurrenceRule.sClock; RecurrenceRule.sClock = mClock; mRecurrenceRuleClockMocked = true; mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); when(mContext.getResources()).thenReturn(mResources); when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); when(mContext.registerReceiverAsUser(mConfigChangeReceiverCaptor.capture(), any(), argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any())) .thenReturn(null); when(mDeps.getClock()).thenReturn(mClock); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); mContentResolver = Mockito.spy(new MockContentResolver(mContext)); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); Settings.Global.clearProviderForTest(); when(mContext.getContentResolver()).thenReturn(mContentResolver); mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM); mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM); mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager); mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI); LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class); LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal); mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps); } @After public void tearDown() { // Avoid setting static clock to null (which should normally not be the case) // if MockitoAnnotations.initMocks threw an exception if (mRecurrenceRuleClockMocked) { RecurrenceRule.sClock = mPreviousRecurrenceRuleClock; } mRecurrenceRuleClockMocked = false; } private void setDefaultQuotaGlobalSetting(long setting) { Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES, (int) setting); } private void testGetMultipathPreference( long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit, long defaultGlobalSetting, long defaultResSetting, boolean roaming) { // TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly. final ZonedDateTime now = ZonedDateTime.ofInstant( Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault()); final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS); when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli()); when(mClock.instant()).thenReturn(now.toInstant()); when(mClock.getZone()).thenReturn(ZoneId.systemDefault()); // Setup plan quota when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH)) .thenReturn(subscriptionQuota); // Setup user policy warning / limit if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) { final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z"); final RecurrenceRule recurrenceRule = new RecurrenceRule( ZonedDateTime.ofInstant( recurrenceStart, ZoneId.systemDefault()), null /* end */, Period.ofMonths(1)); final boolean snoozeWarning = policyWarning == POLICY_SNOOZED; final boolean snoozeLimit = policyLimit == POLICY_SNOOZED; when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] { new NetworkPolicy( NetworkTemplate.buildTemplateMobileWildcard(), recurrenceRule, snoozeWarning ? 0 : policyWarning, snoozeLimit ? 0 : policyLimit, snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, SNOOZE_NEVER, true /* metered */, false /* inferred */) }); } else { when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); } // Setup default quota in settings and resources if (defaultGlobalSetting > 0) { setDefaultQuotaGlobalSetting(defaultGlobalSetting); } when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) .thenReturn((int) defaultResSetting); when(mNetworkStatsManagerInternal.getNetworkTotalBytes( any(), eq(startOfDay.toInstant().toEpochMilli()), eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday); ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); mTracker.start(); verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any()); // Simulate callback after capability changes final NetworkCapabilities capabilities = new NetworkCapabilities() .addCapability(NET_CAPABILITY_INTERNET) .addTransportType(TRANSPORT_CELLULAR) .setNetworkSpecifier(new StringNetworkSpecifier("234")); if (!roaming) { capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); } networkCallback.getValue().onCapabilitiesChanged( TEST_NETWORK, capabilities); } @Test public void testGetMultipathPreference_SubscriptionQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */, DataUnit.MEGABYTES.toBytes(100) /* policyWarning */, LIMIT_DISABLED, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); } @Test public void testGetMultipathPreference_UserWarningQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */, LIMIT_DISABLED, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); } @Test public void testGetMultipathPreference_SnoozedWarningQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st POLICY_SNOOZED /* policyWarning */, DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); } @Test public void testGetMultipathPreference_SnoozedBothQuota() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, // 29 days from Apr. 2nd to May 1st POLICY_SNOOZED /* policyWarning */, POLICY_SNOOZED /* policyLimit */, DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, 2_500_000 /* defaultResSetting */, false /* roaming */); // Default global setting should be used: 12 - 7 = 5 verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any()); } @Test public void testGetMultipathPreference_SettingChanged() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, WARNING_DISABLED, LIMIT_DISABLED, -1 /* defaultGlobalSetting */, DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); // Update setting setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14)); mTracker.mSettingsObserver.onChange( false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES)); // Callback must have been re-registered with new setting verify(mStatsManager, times(1)).unregisterUsageCallback(any()); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); } @Test public void testGetMultipathPreference_ResourceChanged() { testGetMultipathPreference( DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, OPPORTUNISTIC_QUOTA_UNKNOWN, WARNING_DISABLED, LIMIT_DISABLED, -1 /* defaultGlobalSetting */, DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */, false /* roaming */); verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) .thenReturn((int) DataUnit.MEGABYTES.toBytes(16)); final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue(); assertNotNull(configChangeReceiver); configChangeReceiver.onReceive(mContext, new Intent()); // Uses the new setting (16 - 2 = 14MB) verify(mStatsManager, times(1)).registerUsageCallback( any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any()); } }