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

Commit 36b414bc authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Jeff Sharkey
Browse files

More knobs for connectivity experiments.

Keep the same default values in place for calculating opportunistic
quotas, but now allow experiments to override these values.

Tests to verify behavior of defaults.

Bug: 72353440
Test: atest com.android.server.NetworkPolicyManagerServiceTest
Change-Id: I38c225fd141b2e94085ca4cce17ecd51fee3f3aa
parent 2b26f83f
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -8935,6 +8935,20 @@ public final class Settings {
       /** {@hide} */
       public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";

       /** {@hide} */
       public static final String NETPOLICY_QUOTA_ENABLED = "netpolicy_quota_enabled";
       /** {@hide} */
       public static final String NETPOLICY_QUOTA_UNLIMITED = "netpolicy_quota_unlimited";
       /** {@hide} */
       public static final String NETPOLICY_QUOTA_LIMITED = "netpolicy_quota_limited";
       /** {@hide} */
       public static final String NETPOLICY_QUOTA_FRAC_JOBS = "netpolicy_quota_frac_jobs";
       /** {@hide} */
       public static final String NETPOLICY_QUOTA_FRAC_MULTIPATH = "netpolicy_quota_frac_multipath";

       /** {@hide} */
       public static final String NETPOLICY_OVERRIDE_ENABLED = "netpolicy_override_enabled";

       /**
        * User preference for which network(s) should be used. Only the
        * connectivity service should touch this.
+6 −0
Original line number Diff line number Diff line
@@ -300,6 +300,12 @@ public class SettingsBackupTest {
                    Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
                    Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
                    Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
                    Settings.Global.NETPOLICY_QUOTA_ENABLED,
                    Settings.Global.NETPOLICY_QUOTA_UNLIMITED,
                    Settings.Global.NETPOLICY_QUOTA_LIMITED,
                    Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
                    Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
                    Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
                    Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
                    Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+49 −10
Original line number Diff line number Diff line
@@ -70,6 +70,12 @@ import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_LIMITED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_UNLIMITED;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -115,6 +121,7 @@ import android.app.PendingIntent;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -351,6 +358,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     */
    private static final long WAIT_FOR_ADMIN_DATA_TIMEOUT_MS = 10_000;

    private static final long QUOTA_UNLIMITED_DEFAULT = DataUnit.MEBIBYTES.toBytes(20);
    private static final float QUOTA_LIMITED_DEFAULT = 0.1f;
    private static final float QUOTA_FRAC_JOBS_DEFAULT = 0.5f;
    private static final float QUOTA_FRAC_MULTIPATH_DEFAULT = 0.5f;

    private static final int MSG_RULES_CHANGED = 1;
    private static final int MSG_METERED_IFACES_CHANGED = 2;
    private static final int MSG_LIMIT_REACHED = 5;
@@ -1731,10 +1743,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
        mMeteredIfaces = newMeteredIfaces;

        final ContentResolver cr = mContext.getContentResolver();
        final boolean quotaEnabled = Settings.Global.getInt(cr,
                NETPOLICY_QUOTA_ENABLED, 1) != 0;
        final long quotaUnlimited = Settings.Global.getLong(cr,
                NETPOLICY_QUOTA_UNLIMITED, QUOTA_UNLIMITED_DEFAULT);
        final float quotaLimited = Settings.Global.getFloat(cr,
                NETPOLICY_QUOTA_LIMITED, QUOTA_LIMITED_DEFAULT);

        // Finally, calculate our opportunistic quotas
        // TODO: add experiments support to disable or tweak ratios
        mSubscriptionOpportunisticQuota.clear();
        for (NetworkState state : states) {
            if (!quotaEnabled) continue;
            if (state.network == null) continue;
            final int subId = getSubIdLocked(state.network);
            final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
@@ -1746,7 +1766,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                quotaBytes = OPPORTUNISTIC_QUOTA_UNKNOWN;
            } else if (limitBytes == SubscriptionPlan.BYTES_UNLIMITED) {
                // Unlimited data; let's use 20MiB/day (600MiB/month)
                quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
                quotaBytes = quotaUnlimited;
            } else {
                // Limited data; let's only use 10% of remaining budget
                final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
@@ -1764,7 +1784,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                final long remainingDays =
                        1 + ((end - now.toEpochMilli() - 1) / TimeUnit.DAYS.toMillis(1));

                quotaBytes = Math.max(0, (remainingBytes / remainingDays) / 10);
                quotaBytes = Math.max(0, (long) ((remainingBytes / remainingDays) * quotaLimited));
            }

            mSubscriptionOpportunisticQuota.put(subId, quotaBytes);
@@ -3040,6 +3060,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            }
        }

        // Only allow overrides when feature is enabled. However, we always
        // allow disabling of overrides for safety reasons.
        final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
        if (overrideEnabled || overrideValue == 0) {
            mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
                    overrideMask, overrideValue, subId));
            if (timeoutMillis > 0) {
@@ -3047,6 +3072,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                        overrideMask, 0, subId), timeoutMillis);
            }
        }
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -4681,11 +4707,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {

        @Override
        public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
            final long quotaBytes;
            synchronized (mNetworkPoliciesSecondLock) {
                // TODO: handle splitting quota between use-cases
                return mSubscriptionOpportunisticQuota.get(getSubIdLocked(network),
                quotaBytes = mSubscriptionOpportunisticQuota.get(getSubIdLocked(network),
                        OPPORTUNISTIC_QUOTA_UNKNOWN);
            }
            if (quotaBytes == OPPORTUNISTIC_QUOTA_UNKNOWN) {
                return OPPORTUNISTIC_QUOTA_UNKNOWN;
            }

            if (quotaType == QUOTA_TYPE_JOBS) {
                return (long) (quotaBytes * Settings.Global.getFloat(mContext.getContentResolver(),
                        NETPOLICY_QUOTA_FRAC_JOBS, QUOTA_FRAC_JOBS_DEFAULT));
            } else if (quotaType == QUOTA_TYPE_MULTIPATH) {
                return (long) (quotaBytes * Settings.Global.getFloat(mContext.getContentResolver(),
                        NETPOLICY_QUOTA_FRAC_MULTIPATH, QUOTA_FRAC_MULTIPATH_DEFAULT));
            } else {
                return OPPORTUNISTIC_QUOTA_UNKNOWN;
            }
        }

        @Override
+99 −0
Original line number Diff line number Diff line
@@ -37,9 +37,12 @@ import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEF
import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG;
import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
import static android.telephony.SubscriptionPlan.BYTES_UNLIMITED;
import static android.telephony.SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED;
import static android.text.format.Time.TIMEZONE_UTC;

import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
@@ -1484,6 +1487,102 @@ public class NetworkPolicyManagerServiceTest {
                true);
    }

    @Test
    public void testOpportunisticQuota() throws Exception {
        final Network net = new Network(TEST_NET_ID);
        final NetworkPolicyManagerInternal internal = LocalServices
                .getService(NetworkPolicyManagerInternal.class);

        // Create a place to store fake usage
        final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                .thenAnswer(new Answer<Long>() {
                    @Override
                    public Long answer(InvocationOnMock invocation) throws Throwable {
                        final NetworkStatsHistory.Entry entry = history.getValues(
                                invocation.getArgument(1), invocation.getArgument(2), null);
                        return entry.rxBytes + entry.txBytes;
                    }
                });
        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
                .thenAnswer(new Answer<NetworkStats>() {
                    @Override
                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
                        return stats;
                    }
                });

        // Get active mobile network in place
        expectMobileDefaults();
        mService.updateNetworks();

        // We're 20% through the month (6 days)
        final long start = parseTime("2015-11-01T00:00Z");
        final long end = parseTime("2015-11-07T00:00Z");
        setCurrentTimeMillis(end);

        // Get some data usage in place
        history.clear();
        history.recordData(start, end,
                new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));

        // No data plan
        {
            reset(mTelephonyManager, mNetworkManager, mNotifManager);
            expectMobileDefaults();

            mService.updateNetworks();

            // No quotas
            assertEquals(-1, internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_JOBS));
            assertEquals(-1, internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_MULTIPATH));
        }

        // Limited data plan
        {
            final SubscriptionPlan plan = SubscriptionPlan.Builder
                    .createRecurringMonthly(ZonedDateTime.parse("2015-11-01T00:00:00.00Z"))
                    .setDataLimit(DataUnit.MEGABYTES.toBytes(1800), LIMIT_BEHAVIOR_DISABLED)
                    .build();
            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
                    mServiceContext.getOpPackageName());

            reset(mTelephonyManager, mNetworkManager, mNotifManager);
            expectMobileDefaults();

            mService.updateNetworks();

            // We have 1440MB and 24 days left, which is 60MB/day; assuming 10%
            // for quota split equally between two types gives 3MB.
            assertEquals(DataUnit.MEGABYTES.toBytes(3),
                    internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_JOBS));
            assertEquals(DataUnit.MEGABYTES.toBytes(3),
                    internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_MULTIPATH));
        }

        // Unlimited data plan
        {
            final SubscriptionPlan plan = SubscriptionPlan.Builder
                    .createRecurringMonthly(ZonedDateTime.parse("2015-11-01T00:00:00.00Z"))
                    .setDataLimit(BYTES_UNLIMITED, LIMIT_BEHAVIOR_DISABLED)
                    .build();
            mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
                    mServiceContext.getOpPackageName());

            reset(mTelephonyManager, mNetworkManager, mNotifManager);
            expectMobileDefaults();

            mService.updateNetworks();

            // 20MB/day, split equally between two types gives 10MB.
            assertEquals(DataUnit.MEBIBYTES.toBytes(10),
                    internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_JOBS));
            assertEquals(DataUnit.MEBIBYTES.toBytes(10),
                    internal.getSubscriptionOpportunisticQuota(net, QUOTA_TYPE_MULTIPATH));
        }
    }

    private ApplicationInfo buildApplicationInfo(String label) {
        final ApplicationInfo ai = new ApplicationInfo();
        ai.nonLocalizedLabel = label;