Home | History | Annotate | Download | only in connectivity
      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 }
    361