Merge "Alert user on rapid/heavy data usage."
This commit is contained in:
@@ -619,6 +619,10 @@ public class ArrayUtils {
|
||||
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) {
|
||||
return (val != null) ? val : EmptyArray.STRING;
|
||||
}
|
||||
|
||||
@@ -3754,6 +3754,11 @@
|
||||
<!-- Notification body when background data usage is limited. -->
|
||||
<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 -->
|
||||
<!-- Title for an SSL Certificate dialog -->
|
||||
<string name="ssl_certificate">Security certificate</string>
|
||||
|
||||
@@ -1973,6 +1973,8 @@
|
||||
<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_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="device_storage_monitor_notification_channel" />
|
||||
<java-symbol type="string" name="dlg_ok" />
|
||||
|
||||
@@ -193,6 +193,9 @@ message SystemMessage {
|
||||
// Inform the user that Wifi Wake has automatically re-enabled Wifi
|
||||
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
|
||||
// Legacy IDs with arbitrary values appear below
|
||||
// Legacy IDs existed as stable non-conflicting constants prior to the O release
|
||||
|
||||
@@ -99,6 +99,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
@@ -206,8 +207,10 @@ import com.android.server.EventLogTags;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemConfig;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
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;
|
||||
@VisibleForTesting
|
||||
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_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}
|
||||
* 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
|
||||
for (int i = beforeNotifs.size()-1; i >= 0; i--) {
|
||||
final NotificationId notificationId = beforeNotifs.valueAt(i);
|
||||
@@ -1063,7 +1113,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
final SubscriptionManager sub = SubscriptionManager.from(mContext);
|
||||
|
||||
// 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) {
|
||||
final String subscriberId = tele.getSubscriberId(subId);
|
||||
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
|
||||
@@ -1195,6 +1245,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
builder.setContentTitle(title);
|
||||
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);
|
||||
builder.setContentIntent(PendingIntent.getActivity(
|
||||
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}
|
||||
* if necessary.
|
||||
@@ -1471,7 +1541,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
final SubscriptionManager sm = SubscriptionManager.from(mContext);
|
||||
final TelephonyManager tm = TelephonyManager.from(mContext);
|
||||
|
||||
final int[] subIds = sm.getActiveSubscriptionIdList();
|
||||
final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
|
||||
for (int subId : subIds) {
|
||||
final String subscriberId = tm.getSubscriberId(subId);
|
||||
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
|
||||
@@ -1510,7 +1580,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
|
||||
final NetworkState[] states;
|
||||
try {
|
||||
states = mConnManager.getAllNetworkState();
|
||||
states = defeatNullable(mConnManager.getAllNetworkState());
|
||||
} catch (RemoteException e) {
|
||||
// ignored; service lives in system_server
|
||||
return;
|
||||
@@ -1521,7 +1591,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
mNetIdToSubId.clear();
|
||||
final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
|
||||
for (NetworkState state : states) {
|
||||
mNetIdToSubId.put(state.network.netId, parseSubId(state));
|
||||
if (state.network != null) {
|
||||
mNetIdToSubId.put(state.network.netId, parseSubId(state));
|
||||
}
|
||||
if (state.networkInfo != null && state.networkInfo.isConnected()) {
|
||||
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
|
||||
identified.put(state, ident);
|
||||
@@ -1627,23 +1699,23 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
// TODO: add experiments support to disable or tweak ratios
|
||||
mSubscriptionOpportunisticQuota.clear();
|
||||
for (NetworkState state : states) {
|
||||
if (state.network == null) continue;
|
||||
final int subId = getSubIdLocked(state.network);
|
||||
final SubscriptionPlan[] plans = mSubscriptionPlans.get(subId);
|
||||
final SubscriptionPlan plan = ArrayUtils.isEmpty(plans) ? null : plans[0];
|
||||
final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId);
|
||||
if (plan == null) continue;
|
||||
|
||||
// By default assume we have no quota
|
||||
long limitBytes = plan.getDataLimitBytes();
|
||||
long quotaBytes = 0;
|
||||
|
||||
final long limitBytes = plan.getDataLimitBytes();
|
||||
if (limitBytes == SubscriptionPlan.BYTES_UNKNOWN) {
|
||||
// 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)
|
||||
quotaBytes = DataUnit.MEBIBYTES.toBytes(20);
|
||||
} else {
|
||||
// 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 end = cycle.second.toInstant().toEpochMilli();
|
||||
final long totalBytes = getTotalBytes(
|
||||
@@ -1676,7 +1748,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
final TelephonyManager tele = TelephonyManager.from(mContext);
|
||||
final SubscriptionManager sub = SubscriptionManager.from(mContext);
|
||||
|
||||
final int[] subIds = sub.getActiveSubscriptionIdList();
|
||||
final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
|
||||
for (int subId : subIds) {
|
||||
final String subscriberId = tele.getSubscriberId(subId);
|
||||
ensureActiveMobilePolicyAL(subId, subscriberId);
|
||||
@@ -4503,8 +4575,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
@Override
|
||||
public SubscriptionPlan getSubscriptionPlan(Network network) {
|
||||
synchronized (mNetworkPoliciesSecondLock) {
|
||||
final SubscriptionPlan[] plans = mSubscriptionPlans.get(getSubIdLocked(network));
|
||||
return ArrayUtils.isEmpty(plans) ? null : plans[0];
|
||||
final int subId = getSubIdLocked(network);
|
||||
return getPrimarySubscriptionPlanLocked(subId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4537,10 +4609,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
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) {
|
||||
return (uidRules & rule) != 0;
|
||||
}
|
||||
|
||||
private static @NonNull NetworkState[] defeatNullable(@Nullable NetworkState[] val) {
|
||||
return (val != null) ? val : new NetworkState[0];
|
||||
}
|
||||
|
||||
private class NotificationId {
|
||||
private final String mTag;
|
||||
private final int mId;
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server;
|
||||
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
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;
|
||||
@@ -34,6 +35,7 @@ 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.LIMIT_BEHAVIOR_THROTTLED;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
import static android.text.format.Time.TIMEZONE_UTC;
|
||||
|
||||
@@ -62,6 +64,7 @@ import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -86,13 +89,16 @@ import android.net.INetworkManagementEventObserver;
|
||||
import android.net.INetworkPolicyListener;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkInfo.DetailedState;
|
||||
import android.net.NetworkPolicy;
|
||||
import android.net.NetworkState;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.StringNetworkSpecifier;
|
||||
import android.os.Binder;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -105,9 +111,11 @@ import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.Time;
|
||||
import android.util.DataUnit;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.RecurrenceRule;
|
||||
@@ -186,6 +194,7 @@ public class NetworkPolicyManagerServiceTest {
|
||||
private static final long TEST_START = 1194220800000L;
|
||||
private static final String TEST_IFACE = "test0";
|
||||
private static final String TEST_SSID = "AndroidAP";
|
||||
private static final String TEST_IMSI = "310210";
|
||||
|
||||
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
|
||||
|
||||
@@ -309,6 +318,11 @@ public class NetworkPolicyManagerServiceTest {
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforceCallingOrSelfPermission(String permission, String message) {
|
||||
// Assume that we're AID_SYSTEM
|
||||
}
|
||||
};
|
||||
|
||||
setNetpolicyXml(context);
|
||||
@@ -1064,6 +1078,67 @@ public class NetworkPolicyManagerServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRapidNotification() throws Exception {
|
||||
// Create a place to store fake usage
|
||||
final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
// Define simple data plan which gives us effectively 60MB/day
|
||||
final SubscriptionPlan plan = SubscriptionPlan.Builder
|
||||
.createRecurringMonthly(ZonedDateTime.parse("2015-11-01T00:00:00.00Z"))
|
||||
.setDataLimit(DataUnit.MEGABYTES.toBytes(1800), LIMIT_BEHAVIOR_THROTTLED)
|
||||
.build();
|
||||
mService.setSubscriptionPlans(42, new SubscriptionPlan[] { plan },
|
||||
mServiceContext.getOpPackageName());
|
||||
|
||||
// And get that active network in place
|
||||
when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[] {
|
||||
new NetworkState(null, new LinkProperties(),
|
||||
new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR)
|
||||
.setNetworkSpecifier(new StringNetworkSpecifier("42")),
|
||||
new Network(42), TEST_IMSI, null)
|
||||
});
|
||||
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);
|
||||
|
||||
// Using 20% of data in 20% is normal
|
||||
{
|
||||
history.removeBucketsBefore(Long.MAX_VALUE);
|
||||
history.recordData(start, end,
|
||||
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
|
||||
|
||||
reset(mNotifManager);
|
||||
mService.updateNotifications();
|
||||
verify(mNotifManager, never()).enqueueNotificationWithTag(any(), any(), any(),
|
||||
anyInt(), any(), anyInt());
|
||||
}
|
||||
|
||||
// Using 80% data in 20% time is alarming
|
||||
{
|
||||
history.removeBucketsBefore(Long.MAX_VALUE);
|
||||
history.recordData(start, end,
|
||||
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
|
||||
|
||||
reset(mNotifManager);
|
||||
mService.updateNotifications();
|
||||
verify(mNotifManager, atLeastOnce()).enqueueNotificationWithTag(any(), any(), any(),
|
||||
anyInt(), any(), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMeteredNetworkWithoutLimit() throws Exception {
|
||||
NetworkState[] state = null;
|
||||
|
||||
Reference in New Issue
Block a user