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

Commit 2e471457 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Alert user on rapid/heavy data usage.

Now that we have accurate information about a user's carrier data
plan, we can alert them if the current usage patterns would end up
with a nasty surprise towards the end of the current billing cycle.

For example, a single abusive app could use 90% of the user's budget
within the first few days of a billing cycle, leaving the user to
limp along for the remainder of the month.

The simple algorithm here extrapolates to see if the average usage
over the last 4 days would be more than 150% of the data limit for
the full billing cycle.  This period is short enough to catch rapid
recent usage, but long enough to smooth over short-term habit
changes, such as a weekend getaway.  This was chosen after
backtesting the proposed algorithm against real-world data usage
from a handful of internal users.

Fix NPMS unit tests, and write new ones, but leave the existing
@Ignored annotation intact for now.

Test: bit FrameworksServicesTests:com.android.server.NetworkPolicyManagerServiceTest
Bug: 64133169
Change-Id: I0d394b133257e8569a9aa2631b57638839d870ce
parent 9252b340
Loading
Loading
Loading
Loading
+4 −0
Original line number Original line Diff line number Diff line
@@ -619,6 +619,10 @@ public class ArrayUtils {
        return size - leftIdx;
        return size - leftIdx;
    }
    }


    public static @NonNull int[] defeatNullable(@Nullable int[] val) {
        return (val != null) ? val : EmptyArray.INT;
    }

    public static @NonNull String[] defeatNullable(@Nullable String[] val) {
    public static @NonNull String[] defeatNullable(@Nullable String[] val) {
        return (val != null) ? val : EmptyArray.STRING;
        return (val != null) ? val : EmptyArray.STRING;
    }
    }
+5 −0
Original line number Original line Diff line number Diff line
@@ -3754,6 +3754,11 @@
    <!-- Notification body when background data usage is limited. -->
    <!-- Notification body when background data usage is limited. -->
    <string name="data_usage_restricted_body">Tap to remove restriction.</string>
    <string name="data_usage_restricted_body">Tap to remove restriction.</string>


    <!-- Notification title when there has been recent excessive data usage. [CHAR LIMIT=32] -->
    <string name="data_usage_rapid_title">Large data usage</string>
    <!-- Notification body when there has been recent excessive data usage. [CHAR LIMIT=128] -->
    <string name="data_usage_rapid_body">Your data usage over the last few days is larger than normal. Tap to view usage and settings.</string>

    <!-- SSL Certificate dialogs -->
    <!-- SSL Certificate dialogs -->
    <!-- Title for an SSL Certificate dialog -->
    <!-- Title for an SSL Certificate dialog -->
    <string name="ssl_certificate">Security certificate</string>
    <string name="ssl_certificate">Security certificate</string>
+2 −0
Original line number Original line Diff line number Diff line
@@ -1973,6 +1973,8 @@
  <java-symbol type="string" name="data_usage_warning_title" />
  <java-symbol type="string" name="data_usage_warning_title" />
  <java-symbol type="string" name="data_usage_wifi_limit_snoozed_title" />
  <java-symbol type="string" name="data_usage_wifi_limit_snoozed_title" />
  <java-symbol type="string" name="data_usage_wifi_limit_title" />
  <java-symbol type="string" name="data_usage_wifi_limit_title" />
  <java-symbol type="string" name="data_usage_rapid_title" />
  <java-symbol type="string" name="data_usage_rapid_body" />
  <java-symbol type="string" name="default_wallpaper_component" />
  <java-symbol type="string" name="default_wallpaper_component" />
  <java-symbol type="string" name="device_storage_monitor_notification_channel" />
  <java-symbol type="string" name="device_storage_monitor_notification_channel" />
  <java-symbol type="string" name="dlg_ok" />
  <java-symbol type="string" name="dlg_ok" />
+3 −0
Original line number Original line Diff line number Diff line
@@ -193,6 +193,9 @@ message SystemMessage {
    // Inform the user that Wifi Wake has automatically re-enabled Wifi
    // Inform the user that Wifi Wake has automatically re-enabled Wifi
    NOTE_WIFI_WAKE_TURNED_BACK_ON = 44;
    NOTE_WIFI_WAKE_TURNED_BACK_ON = 44;


    // Inform the user that unexpectedly rapid network usage is happening
    NOTE_NET_RAPID = 45;

    // ADD_NEW_IDS_ABOVE_THIS_LINE
    // ADD_NEW_IDS_ABOVE_THIS_LINE
    // Legacy IDs with arbitrary values appear below
    // Legacy IDs with arbitrary values appear below
    // Legacy IDs existed as stable non-conflicting constants prior to the O release
    // Legacy IDs existed as stable non-conflicting constants prior to the O release
+93 −12
Original line number Original line Diff line number Diff line
@@ -99,6 +99,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;


import android.Manifest;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
@@ -206,8 +207,10 @@ import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.SystemConfig;
import com.android.server.SystemService;


import libcore.io.IoUtils;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;


import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlSerializer;
@@ -283,6 +286,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    public static final int TYPE_LIMIT = SystemMessage.NOTE_NET_LIMIT;
    public static final int TYPE_LIMIT = SystemMessage.NOTE_NET_LIMIT;
    @VisibleForTesting
    @VisibleForTesting
    public static final int TYPE_LIMIT_SNOOZED = SystemMessage.NOTE_NET_LIMIT_SNOOZED;
    public static final int TYPE_LIMIT_SNOOZED = SystemMessage.NOTE_NET_LIMIT_SNOOZED;
    @VisibleForTesting
    public static final int TYPE_RAPID = SystemMessage.NOTE_NET_RAPID;


    private static final String TAG_POLICY_LIST = "policy-list";
    private static final String TAG_POLICY_LIST = "policy-list";
    private static final String TAG_NETWORK_POLICY = "network-policy";
    private static final String TAG_NETWORK_POLICY = "network-policy";
@@ -998,6 +1003,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
        }
    };
    };


    @VisibleForTesting
    public void updateNotifications() {
        synchronized (mNetworkPoliciesSecondLock) {
            updateNotificationsNL();
        }
    }

    /**
    /**
     * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
     * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
     * to show visible notifications as needed.
     * to show visible notifications as needed.
@@ -1042,6 +1054,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            }
            }
        }
        }


        // Alert the user about heavy recent data usage that might result in
        // going over their carrier limit.
        for (int i = 0; i < mNetIdToSubId.size(); i++) {
            final int subId = mNetIdToSubId.valueAt(i);
            final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
            if (plan == null) continue;

            final long limitBytes = plan.getDataLimitBytes();
            if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
                // Ignore missing limits
            } else if (limitBytes == SubscriptionPlan.BYTES_UNLIMITED) {
                // Unlimited data; no rapid usage alerting
            } else {
                // Warn if average usage over last 4 days is on track to blow
                // pretty far past the plan limits.
                final long recentDuration = TimeUnit.DAYS.toMillis(4);
                final long end = RecurrenceRule.sClock.millis();
                final long start = end - recentDuration;

                final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(
                        mContext.getSystemService(TelephonyManager.class).getSubscriberId(subId));
                final long recentBytes = getTotalBytes(template, start, end);

                final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
                final long cycleDuration = cycle.second.toInstant().toEpochMilli()
                        - cycle.first.toInstant().toEpochMilli();

                final long projectedBytes = (recentBytes * cycleDuration) / recentDuration;
                final long alertBytes = (limitBytes * 3) / 2;
                if (projectedBytes > alertBytes) {
                    final NetworkPolicy policy = new NetworkPolicy(template, plan.getCycleRule(),
                            NetworkPolicy.WARNING_DISABLED, NetworkPolicy.LIMIT_DISABLED,
                            NetworkPolicy.SNOOZE_NEVER, NetworkPolicy.SNOOZE_NEVER, true, true);
                    enqueueNotification(policy, TYPE_RAPID, 0);
                }
            }
        }

        // cancel stale notifications that we didn't renew above
        // cancel stale notifications that we didn't renew above
        for (int i = beforeNotifs.size()-1; i >= 0; i--) {
        for (int i = beforeNotifs.size()-1; i >= 0; i--) {
            final NotificationId notificationId = beforeNotifs.valueAt(i);
            final NotificationId notificationId = beforeNotifs.valueAt(i);
@@ -1063,7 +1113,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            final SubscriptionManager sub = SubscriptionManager.from(mContext);
            final SubscriptionManager sub = SubscriptionManager.from(mContext);


            // Mobile template is relevant when any active subscriber matches
            // Mobile template is relevant when any active subscriber matches
            final int[] subIds = sub.getActiveSubscriptionIdList();
            final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
            for (int subId : subIds) {
            for (int subId : subIds) {
                final String subscriberId = tele.getSubscriberId(subId);
                final String subscriberId = tele.getSubscriberId(subId);
                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
@@ -1195,6 +1245,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                builder.setContentTitle(title);
                builder.setContentTitle(title);
                builder.setContentText(body);
                builder.setContentText(body);


                final Intent intent = buildViewDataUsageIntent(res, policy.template);
                builder.setContentIntent(PendingIntent.getActivity(
                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                break;
            }
            case TYPE_RAPID: {
                final CharSequence title = res.getText(R.string.data_usage_rapid_title);
                body = res.getText(R.string.data_usage_rapid_body);

                builder.setOngoing(true);
                builder.setSmallIcon(R.drawable.stat_notify_error);
                builder.setTicker(title);
                builder.setContentTitle(title);
                builder.setContentText(body);

                final Intent intent = buildViewDataUsageIntent(res, policy.template);
                final Intent intent = buildViewDataUsageIntent(res, policy.template);
                builder.setContentIntent(PendingIntent.getActivity(
                builder.setContentIntent(PendingIntent.getActivity(
                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
@@ -1253,6 +1318,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
        }
    };
    };


    @VisibleForTesting
    public void updateNetworks() {
        mConnReceiver.onReceive(null, null);
    }

    /**
    /**
     * Update mobile policies with data cycle information from {@link CarrierConfigManager}
     * Update mobile policies with data cycle information from {@link CarrierConfigManager}
     * if necessary.
     * if necessary.
@@ -1471,7 +1541,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            final SubscriptionManager sm = SubscriptionManager.from(mContext);
            final SubscriptionManager sm = SubscriptionManager.from(mContext);
            final TelephonyManager tm = TelephonyManager.from(mContext);
            final TelephonyManager tm = TelephonyManager.from(mContext);


            final int[] subIds = sm.getActiveSubscriptionIdList();
            final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
            for (int subId : subIds) {
            for (int subId : subIds) {
                final String subscriberId = tm.getSubscriberId(subId);
                final String subscriberId = tm.getSubscriberId(subId);
                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
@@ -1510,7 +1580,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {


        final NetworkState[] states;
        final NetworkState[] states;
        try {
        try {
            states = mConnManager.getAllNetworkState();
            states = defeatNullable(mConnManager.getAllNetworkState());
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            // ignored; service lives in system_server
            // ignored; service lives in system_server
            return;
            return;
@@ -1521,7 +1591,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        mNetIdToSubId.clear();
        mNetIdToSubId.clear();
        final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
        final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
        for (NetworkState state : states) {
        for (NetworkState state : states) {
            if (state.network != null) {
                mNetIdToSubId.put(state.network.netId, parseSubId(state));
                mNetIdToSubId.put(state.network.netId, parseSubId(state));
            }
            if (state.networkInfo != null && state.networkInfo.isConnected()) {
            if (state.networkInfo != null && state.networkInfo.isConnected()) {
                final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
                final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
                identified.put(state, ident);
                identified.put(state, ident);
@@ -1627,23 +1699,23 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        // TODO: add experiments support to disable or tweak ratios
        // TODO: add experiments support to disable or tweak ratios
        mSubscriptionOpportunisticQuota.clear();
        mSubscriptionOpportunisticQuota.clear();
        for (NetworkState state : states) {
        for (NetworkState state : states) {
            if (state.network == null) continue;
            final int subId = getSubIdLocked(state.network);
            final int subId = getSubIdLocked(state.network);
            final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
            final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
            final SubscriptionPlan plan = ArrayUtils.isEmpty(plans) ? null : plans[0];
            if (plan == null) continue;
            if (plan == null) continue;


            // By default assume we have no quota
            // By default assume we have no quota
            long limitBytes = plan.getDataLimitBytes();
            long quotaBytes = 0;
            long quotaBytes = 0;


            final long limitBytes = plan.getDataLimitBytes();
            if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
            if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
                // Ignore missing limits
                // Ignore missing limits
            } else if (plan.getDataLimitBytes() == SubscriptionPlan.BYTES_UNLIMITED) {
            } else if (limitBytes == SubscriptionPlan.BYTES_UNLIMITED) {
                // Unlimited data; let's use 20MiB/day (600MiB/month)
                // Unlimited data; let's use 20MiB/day (600MiB/month)
                quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
                quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
            } else {
            } else {
                // Limited data; let's only use 10% of remaining budget
                // Limited data; let's only use 10% of remaining budget
                final Pair<ZonedDateTime, ZonedDateTime> cycle = plans[0].cycleIterator().next();
                final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
                final long start = cycle.first.toInstant().toEpochMilli();
                final long start = cycle.first.toInstant().toEpochMilli();
                final long end = cycle.second.toInstant().toEpochMilli();
                final long end = cycle.second.toInstant().toEpochMilli();
                final long totalBytes = getTotalBytes(
                final long totalBytes = getTotalBytes(
@@ -1676,7 +1748,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        final TelephonyManager tele = TelephonyManager.from(mContext);
        final TelephonyManager tele = TelephonyManager.from(mContext);
        final SubscriptionManager sub = SubscriptionManager.from(mContext);
        final SubscriptionManager sub = SubscriptionManager.from(mContext);


        final int[] subIds = sub.getActiveSubscriptionIdList();
        final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
        for (int subId : subIds) {
        for (int subId : subIds) {
            final String subscriberId = tele.getSubscriberId(subId);
            final String subscriberId = tele.getSubscriberId(subId);
            ensureActiveMobilePolicyAL(subId, subscriberId);
            ensureActiveMobilePolicyAL(subId, subscriberId);
@@ -4503,8 +4575,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        @Override
        @Override
        public SubscriptionPlan getSubscriptionPlan(Network network) {
        public SubscriptionPlan getSubscriptionPlan(Network network) {
            synchronized (mNetworkPoliciesSecondLock) {
            synchronized (mNetworkPoliciesSecondLock) {
                final SubscriptionPlan[] plans = mSubscriptionPlans.get(getSubIdLocked(network));
                final int subId = getSubIdLocked(network);
                return ArrayUtils.isEmpty(plans) ? null : plans[0];
                return getPrimarySubscriptionPlanLocked(subId);
            }
            }
        }
        }


@@ -4537,10 +4609,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        return mNetIdToSubId.get(network.netId, INVALID_SUBSCRIPTION_ID);
        return mNetIdToSubId.get(network.netId, INVALID_SUBSCRIPTION_ID);
    }
    }


    private SubscriptionPlan getPrimarySubscriptionPlanLocked(int subId) {
        final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
        return ArrayUtils.isEmpty(plans) ? null : plans[0];
    }

    private static boolean hasRule(int uidRules, int rule) {
    private static boolean hasRule(int uidRules, int rule) {
        return (uidRules & rule) != 0;
        return (uidRules & rule) != 0;
    }
    }


    private static @NonNull NetworkState[] defeatNullable(@Nullable NetworkState[] val) {
        return (val != null) ? val : new NetworkState[0];
    }

    private class NotificationId {
    private class NotificationId {
        private final String mTag;
        private final String mTag;
        private final int mId;
        private final int mId;
Loading