Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cf54c045 authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Android (Google) Code Review
Browse files

Merge "Added tests for MultipathPolicyTracker." into pi-dev

parents c7292ffa 6a7a5a1c
Loading
Loading
Loading
Loading
+24 −8
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

@@ -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;
@@ -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);
@@ -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
+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());
    }
}