public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
private static final int VERSION_INIT = 1;
private static final int VERSION_RULE = 2;
+ private static final int VERSION_RAPID = 3;
public static final int CYCLE_NONE = -1;
public static final long WARNING_DISABLED = -1;
public long limitBytes = LIMIT_DISABLED;
public long lastWarningSnooze = SNOOZE_NEVER;
public long lastLimitSnooze = SNOOZE_NEVER;
+ public long lastRapidSnooze = SNOOZE_NEVER;
@Deprecated public boolean metered = true;
public boolean inferred = false;
limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
}
+ @Deprecated
public NetworkPolicy(NetworkTemplate template, RecurrenceRule cycleRule, long warningBytes,
long limitBytes, long lastWarningSnooze, long lastLimitSnooze, boolean metered,
boolean inferred) {
+ this(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze,
+ SNOOZE_NEVER, metered, inferred);
+ }
+
+ public NetworkPolicy(NetworkTemplate template, RecurrenceRule cycleRule, long warningBytes,
+ long limitBytes, long lastWarningSnooze, long lastLimitSnooze, long lastRapidSnooze,
+ boolean metered, boolean inferred) {
this.template = Preconditions.checkNotNull(template, "missing NetworkTemplate");
this.cycleRule = Preconditions.checkNotNull(cycleRule, "missing RecurrenceRule");
this.warningBytes = warningBytes;
this.limitBytes = limitBytes;
this.lastWarningSnooze = lastWarningSnooze;
this.lastLimitSnooze = lastLimitSnooze;
+ this.lastRapidSnooze = lastRapidSnooze;
this.metered = metered;
this.inferred = inferred;
}
limitBytes = source.readLong();
lastWarningSnooze = source.readLong();
lastLimitSnooze = source.readLong();
+ lastRapidSnooze = source.readLong();
metered = source.readInt() != 0;
inferred = source.readInt() != 0;
}
dest.writeLong(limitBytes);
dest.writeLong(lastWarningSnooze);
dest.writeLong(lastLimitSnooze);
+ dest.writeLong(lastRapidSnooze);
dest.writeInt(metered ? 1 : 0);
dest.writeInt(inferred ? 1 : 0);
}
public void clearSnooze() {
lastWarningSnooze = SNOOZE_NEVER;
lastLimitSnooze = SNOOZE_NEVER;
+ lastRapidSnooze = SNOOZE_NEVER;
}
/**
@Override
public int hashCode() {
return Objects.hash(template, cycleRule, warningBytes, limitBytes,
- lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ lastWarningSnooze, lastLimitSnooze, lastRapidSnooze, metered, inferred);
}
@Override
return warningBytes == other.warningBytes
&& limitBytes == other.limitBytes
&& lastWarningSnooze == other.lastWarningSnooze
- && lastLimitSnooze == other.lastLimitSnooze && metered == other.metered
+ && lastLimitSnooze == other.lastLimitSnooze
+ && lastRapidSnooze == other.lastRapidSnooze
+ && metered == other.metered
&& inferred == other.inferred
&& Objects.equals(template, other.template)
&& Objects.equals(cycleRule, other.cycleRule);
.append(" limitBytes=").append(limitBytes)
.append(" lastWarningSnooze=").append(lastWarningSnooze)
.append(" lastLimitSnooze=").append(lastLimitSnooze)
+ .append(" lastRapidSnooze=").append(lastRapidSnooze)
.append(" metered=").append(metered)
.append(" inferred=").append(inferred)
.append("}").toString();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
- out.writeInt(VERSION_RULE);
+ out.writeInt(VERSION_RAPID);
out.write(template.getBytesForBackup());
cycleRule.writeToStream(out);
out.writeLong(warningBytes);
out.writeLong(limitBytes);
out.writeLong(lastWarningSnooze);
out.writeLong(lastLimitSnooze);
+ out.writeLong(lastRapidSnooze);
out.writeInt(metered ? 1 : 0);
out.writeInt(inferred ? 1 : 0);
return baos.toByteArray();
public static NetworkPolicy getNetworkPolicyFromBackup(DataInputStream in) throws IOException,
BackupUtils.BadVersionException {
final int version = in.readInt();
- switch (version) {
- case VERSION_INIT: {
- NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
- int cycleDay = in.readInt();
- String cycleTimeZone = BackupUtils.readString(in);
- long warningBytes = in.readLong();
- long limitBytes = in.readLong();
- long lastWarningSnooze = in.readLong();
- long lastLimitSnooze = in.readLong();
- boolean metered = in.readInt() == 1;
- boolean inferred = in.readInt() == 1;
- return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes,
- limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
- }
- case VERSION_RULE: {
- NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
- RecurrenceRule cycleRule = new RecurrenceRule(in);
- long warningBytes = in.readLong();
- long limitBytes = in.readLong();
- long lastWarningSnooze = in.readLong();
- long lastLimitSnooze = in.readLong();
- boolean metered = in.readInt() == 1;
- boolean inferred = in.readInt() == 1;
- return new NetworkPolicy(template, cycleRule, warningBytes,
- limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
- }
- default: {
- throw new BackupUtils.BadVersionException("Unknown backup version: " + version);
- }
+ if (version > VERSION_RAPID) {
+ throw new BackupUtils.BadVersionException("Unknown backup version: " + version);
+ }
+
+ final NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
+ final RecurrenceRule cycleRule;
+ if (version >= VERSION_RULE) {
+ cycleRule = new RecurrenceRule(in);
+ } else {
+ final int cycleDay = in.readInt();
+ final String cycleTimezone = BackupUtils.readString(in);
+ cycleRule = buildRule(cycleDay, ZoneId.of(cycleTimezone));
+ }
+ final long warningBytes = in.readLong();
+ final long limitBytes = in.readLong();
+ final long lastWarningSnooze = in.readLong();
+ final long lastLimitSnooze = in.readLong();
+ final long lastRapidSnooze;
+ if (version >= VERSION_RAPID) {
+ lastRapidSnooze = in.readLong();
+ } else {
+ lastRapidSnooze = SNOOZE_NEVER;
}
+ final boolean metered = in.readInt() == 1;
+ final boolean inferred = in.readInt() == 1;
+ return new NetworkPolicy(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze,
+ lastLimitSnooze, lastRapidSnooze, metered, inferred);
}
}
<protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
<protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
<protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
+ <protected-broadcast android:name="com.android.server.net.action.SNOOZE_RAPID" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
<protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
-import android.app.INotificationManager;
import android.app.IUidObserver;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
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;
"com.android.server.net.action.ALLOW_BACKGROUND";
private static final String ACTION_SNOOZE_WARNING =
"com.android.server.net.action.SNOOZE_WARNING";
+ private static final String ACTION_SNOOZE_RAPID =
+ "com.android.server.net.action.SNOOZE_RAPID";
private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
private final CarrierConfigManager mCarrierConfigManager;
private IConnectivityManager mConnManager;
- private INotificationManager mNotifManager;
private PowerManagerInternal mPowerManagerInternal;
private IDeviceIdleController mDeviceIdleController;
@GuardedBy("mUidRulesFirstLock")
mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
}
- public void bindNotificationManager(INotificationManager notifManager) {
- mNotifManager = checkNotNull(notifManager, "missing INotificationManager");
- }
-
void updatePowerSaveWhitelistUL() {
try {
int[] whitelist = mDeviceIdleController.getAppIdWhitelistExceptIdle();
final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
- // listen for snooze warning from notifications
- final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING);
- mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter,
- MANAGE_NETWORK_POLICY, mHandler);
+ // Listen for snooze from notifications
+ mContext.registerReceiver(mSnoozeReceiver,
+ new IntentFilter(ACTION_SNOOZE_WARNING), MANAGE_NETWORK_POLICY, mHandler);
+ mContext.registerReceiver(mSnoozeReceiver,
+ new IntentFilter(ACTION_SNOOZE_RAPID), MANAGE_NETWORK_POLICY, mHandler);
// listen for configured wifi networks to be loaded
final IntentFilter wifiFilter =
* Receiver that watches for {@link Notification} control of
* {@link NetworkPolicy#lastWarningSnooze}.
*/
- final private BroadcastReceiver mSnoozeWarningReceiver = new BroadcastReceiver() {
+ final private BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// on background handler thread, and verified MANAGE_NETWORK_POLICY
// permission above.
final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
- performSnooze(template, TYPE_WARNING);
+ if (ACTION_SNOOZE_WARNING.equals(intent.getAction())) {
+ performSnooze(template, TYPE_WARNING);
+ } else if (ACTION_SNOOZE_RAPID.equals(intent.getAction())) {
+ performSnooze(template, TYPE_RAPID);
+ }
}
};
}
};
- @VisibleForTesting
- public void updateNotifications() {
- synchronized (mNetworkPoliciesSecondLock) {
- updateNotificationsNL();
- }
- }
-
/**
* Check {@link NetworkPolicy} against current {@link INetworkStatsService}
* to show visible notifications as needed.
// cycle boundary to recompute notifications.
// examine stats for each active policy
+ final long now = currentTimeMillis();
for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
// ignore policies that aren't relevant to user
final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
.cycleIterator(policy).next();
- final long start = cycle.first.toInstant().toEpochMilli();
- final long end = cycle.second.toInstant().toEpochMilli();
- final long totalBytes = getTotalBytes(policy.template, start, end);
+ final long cycleStart = cycle.first.toInstant().toEpochMilli();
+ final long cycleEnd = cycle.second.toInstant().toEpochMilli();
+ final long totalBytes = getTotalBytes(policy.template, cycleStart, cycleEnd);
+ // Notify when data usage is over warning/limit
if (policy.isOverLimit(totalBytes)) {
- if (policy.lastLimitSnooze >= start) {
+ final boolean snoozedThisCycle = policy.lastLimitSnooze >= cycleStart;
+ if (snoozedThisCycle) {
enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
} else {
enqueueNotification(policy, TYPE_LIMIT, totalBytes);
} else {
notifyUnderLimitNL(policy.template);
- if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) {
+ final boolean snoozedThisCycle = policy.lastWarningSnooze >= cycleStart;
+ if (policy.isOverWarning(totalBytes) && !snoozedThisCycle) {
enqueueNotification(policy, TYPE_WARNING, totalBytes);
}
}
- }
- // 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.
+ // Warn if average usage over last 4 days is on track to blow pretty
+ // far past the plan limits.
+ if (policy.limitBytes != LIMIT_DISABLED) {
final long recentDuration = TimeUnit.DAYS.toMillis(4);
- final long end = RecurrenceRule.sClock.millis();
- final long start = end - recentDuration;
+ final long recentBytes = getTotalBytes(policy.template, now - recentDuration, now);
- final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(
- mContext.getSystemService(TelephonyManager.class).getSubscriberId(subId));
- final long recentBytes = getTotalBytes(template, start, end);
+ final long cycleDuration = cycleEnd - cycleStart;
+ final long projectedBytes = (recentBytes * cycleDuration) / recentDuration;
+ final long alertBytes = (policy.limitBytes * 3) / 2;
- final Pair<ZonedDateTime, ZonedDateTime> cycle = plan.cycleIterator().next();
- final long cycleDuration = cycle.second.toInstant().toEpochMilli()
- - cycle.first.toInstant().toEpochMilli();
+ if (LOGD) {
+ Slog.d(TAG, "Rapid usage considering recent " + recentBytes + " projected "
+ + projectedBytes + " alert " + alertBytes);
+ }
- 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);
+ final boolean snoozedRecently = policy.lastRapidSnooze >= now
+ - DateUtils.DAY_IN_MILLIS;
+ if (projectedBytes > alertBytes && !snoozedRecently) {
enqueueNotification(policy, TYPE_RAPID, 0);
}
}
*/
private boolean isTemplateRelevant(NetworkTemplate template) {
if (template.isMatchRuleMobile()) {
- final TelephonyManager tele = TelephonyManager.from(mContext);
- final SubscriptionManager sub = SubscriptionManager.from(mContext);
+ final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
+ final SubscriptionManager sub = mContext.getSystemService(SubscriptionManager.class);
// Mobile template is relevant when any active subscriber matches
final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes) {
final NotificationId notificationId = new NotificationId(policy, type);
final Notification.Builder builder =
- new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_STATUS);
+ new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_ALERTS);
builder.setOnlyAlertOnce(true);
builder.setWhen(0L);
builder.setColor(mContext.getColor(
builder.setTicker(title);
builder.setContentTitle(title);
builder.setContentText(body);
- builder.setDefaults(Notification.DEFAULT_ALL);
- builder.setChannelId(SystemNotificationChannels.NETWORK_ALERTS);
final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template);
builder.setDeleteIntent(PendingIntent.getBroadcast(
builder.setTicker(title);
builder.setContentTitle(title);
builder.setContentText(body);
+ builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS);
final Intent intent = buildViewDataUsageIntent(res, policy.template);
builder.setContentIntent(PendingIntent.getActivity(
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 snoozeIntent = buildSnoozeRapidIntent(policy.template);
+ builder.setDeleteIntent(PendingIntent.getBroadcast(
+ mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ final Intent viewIntent = buildViewDataUsageIntent(res, policy.template);
builder.setContentIntent(PendingIntent.getActivity(
- mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+ mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT));
break;
}
}
- // TODO: move to NotificationManager once we can mock it
- try {
- final String packageName = mContext.getPackageName();
- if (!TextUtils.isEmpty(body)) {
- builder.setStyle(new Notification.BigTextStyle()
- .bigText(body));
- }
- mNotifManager.enqueueNotificationWithTag(
- packageName, packageName, notificationId.getTag(), notificationId.getId(),
- builder.build(), UserHandle.USER_ALL);
- mActiveNotifs.add(notificationId);
- } catch (RemoteException e) {
- // ignored; service lives in system_server
+ if (!TextUtils.isEmpty(body)) {
+ builder.setStyle(new Notification.BigTextStyle().bigText(body));
}
+
+ mContext.getSystemService(NotificationManager.class).notifyAsUser(notificationId.getTag(),
+ notificationId.getId(), builder.build(), UserHandle.ALL);
+ mActiveNotifs.add(notificationId);
}
private void cancelNotification(NotificationId notificationId) {
- // TODO: move to NotificationManager once we can mock it
- try {
- final String packageName = mContext.getPackageName();
- mNotifManager.cancelNotificationWithTag(
- packageName, notificationId.getTag(), notificationId.getId(),
- UserHandle.USER_ALL);
- } catch (RemoteException e) {
- // ignored; service lives in system_server
- }
+ mContext.getSystemService(NotificationManager.class).cancel(notificationId.getTag(),
+ notificationId.getId());
}
/**
};
@VisibleForTesting
- public void updateNetworks() {
+ public void updateNetworks() throws InterruptedException {
mConnReceiver.onReceive(null, null);
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ latch.countDown();
+ });
+ latch.await(5, TimeUnit.SECONDS);
}
/**
if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleAL()");
boolean policyUpdated = false;
- final String subscriberId = TelephonyManager.from(mContext).getSubscriberId(subId);
+ final String subscriberId = mContext.getSystemService(TelephonyManager.class)
+ .getSubscriberId(subId);
// find and update the mobile NetworkPolicy for this subscriber id
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
return;
}
final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, -1);
- final TelephonyManager tele = TelephonyManager.from(mContext);
+ final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
final String subscriberId = tele.getSubscriberId(subId);
maybeRefreshTrustedTime();
if (template.getMatchRule() == MATCH_MOBILE_ALL) {
// If mobile data usage hits the limit or if the user resumes the data, we need to
// notify telephony.
- final SubscriptionManager sm = SubscriptionManager.from(mContext);
- final TelephonyManager tm = TelephonyManager.from(mContext);
+ final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
for (int subId : subIds) {
final long totalBytes = getTotalBytes(
NetworkTemplate.buildTemplateMobileAll(state.subscriberId), start, end);
final long remainingBytes = limitBytes - totalBytes;
- final long remainingDays = Math.min(1, (end - RecurrenceRule.sClock.millis())
+ final long remainingDays = Math.min(1, (end - currentTimeMillis())
/ TimeUnit.DAYS.toMillis(1));
if (remainingBytes > 0) {
quotaBytes = (remainingBytes / remainingDays) / 10;
if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyAL()");
if (mSuppressDefaultPolicy) return;
- final TelephonyManager tele = TelephonyManager.from(mContext);
- final SubscriptionManager sub = SubscriptionManager.from(mContext);
+ final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
+ final SubscriptionManager sub = mContext.getSystemService(SubscriptionManager.class);
final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
for (int subId : subIds) {
}
private void normalizePoliciesNL(NetworkPolicy[] policies) {
- final TelephonyManager tele = TelephonyManager.from(mContext);
+ final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
final String[] merged = tele.getMergedSubscriberIds();
mNetworkPolicy.clear();
case TYPE_LIMIT:
policy.lastLimitSnooze = currentTime;
break;
+ case TYPE_RAPID:
+ policy.lastRapidSnooze = currentTime;
+ break;
default:
throw new IllegalArgumentException("unexpected type");
}
private static Intent buildSnoozeWarningIntent(NetworkTemplate template) {
final Intent intent = new Intent(ACTION_SNOOZE_WARNING);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+ return intent;
+ }
+
+ private static Intent buildSnoozeRapidIntent(NetworkTemplate template) {
+ final Intent intent = new Intent(ACTION_SNOOZE_RAPID);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
return intent;
}
SystemNotificationChannels.createAll(context);
notification = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- networkPolicy.bindNotificationManager(notification);
traceEnd();
traceBeginAndSlog("StartDeviceMonitor");
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.uidPoliciesToString;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.TrafficStats.KB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
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.telephony.SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED;
import static android.text.format.Time.TIMEZONE_UTC;
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;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
-import android.app.INotificationManager;
import android.app.IUidObserver;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
-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.test.suitebuilder.annotation.MediumTest;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.DataUnit;
private static final String TEST_IFACE = "test0";
private static final String TEST_SSID = "AndroidAP";
private static final String TEST_IMSI = "310210";
+ private static final int TEST_SUB_ID = 42;
+ private static final int TEST_NET_ID = 24;
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
private @Mock INetworkManagementService mNetworkManager;
private @Mock TrustedTime mTime;
private @Mock IConnectivityManager mConnManager;
- private @Mock INotificationManager mNotifManager;
+ private @Mock NotificationManager mNotifManager;
private @Mock PackageManager mPackageManager;
private @Mock IPackageManager mIpm;
private @Mock SubscriptionManager mSubscriptionManager;
return mCarrierConfigManager;
case Context.TELEPHONY_SERVICE:
return mTelephonyManager;
+ case Context.NOTIFICATION_SERVICE:
+ return mNotifManager;
default:
return super.getSystemService(name);
}
mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mStatsService,
mNetworkManager, mIpm, mTime, mPolicyDir, true);
mService.bindConnectivityManager(mConnManager);
- mService.bindNotificationManager(mNotifManager);
mPolicyListener = new NetworkPolicyListenerAnswer(mService);
// Sets some common expectations.
sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
- // TODO: consider making strongly ordered mock
- verifyPolicyDataEnable(TYPE_WIFI, true);
- verifyRemoveInterfaceQuota(TEST_IFACE);
- verifySetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+ (2 * MB_IN_BYTES) - 512);
}
@Test
- public void testOverWarningLimitNotification() throws Exception {
- NetworkState[] state = null;
- NetworkStats stats = null;
- Future<String> tagFuture = null;
+ public void testNotificationWarningLimitSnooze() 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;
+ }
+ });
- final int CYCLE_DAY = 15;
- final long NOW = parseTime("2007-03-10T00:00Z");
- final long CYCLE_START = parseTime("2007-02-15T00:00Z");
- final long CYCLE_END = parseTime("2007-03-15T00:00Z");
+ // Get active mobile network in place
+ expectMobileDefaults();
+ mService.updateNetworks();
- setCurrentTimeMillis(NOW);
+ // Define simple 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());
- // assign wifi policy
- state = new NetworkState[] {};
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L);
+ // 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);
+ expectCurrentTime();
+ // Normal usage means no notification
{
- expectCurrentTime();
- when(mConnManager.getAllNetworkState()).thenReturn(state);
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START,
- CYCLE_END)).thenReturn(stats.getTotalBytes());
-
- mPolicyListener.expect().onMeteredIfacesChanged(any());
- setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1
- * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
- mPolicyListener.waitAndVerify().onMeteredIfacesChanged(any());
- verifyPolicyDataEnable(TYPE_WIFI, true);
- }
-
- // bring up wifi network
- incrementCurrentTime(MINUTE_IN_MILLIS);
- state = new NetworkState[] { buildWifi() };
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L);
+ history.removeBucketsBefore(Long.MAX_VALUE);
+ history.recordData(start, end,
+ new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
- {
- expectCurrentTime();
- when(mConnManager.getAllNetworkState()).thenReturn(state);
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START,
- CYCLE_END)).thenReturn(stats.getTotalBytes());
+ reset(mTelephonyManager, mNetworkManager, mNotifManager);
+ expectMobileDefaults();
- mPolicyListener.expect().onMeteredIfacesChanged(any());
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
- mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+ mService.updateNetworks();
- verifyPolicyDataEnable(TYPE_WIFI, true);
- verifyRemoveInterfaceQuota(TEST_IFACE);
- verifySetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
+ verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+ DataUnit.MEGABYTES.toBytes(1800 - 360));
+ verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
}
- // go over warning, which should kick notification
- incrementCurrentTime(MINUTE_IN_MILLIS);
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 1536 * KB_IN_BYTES, 15L, 0L, 0L);
-
+ // Push over warning
{
- expectCurrentTime();
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START,
- CYCLE_END)).thenReturn(stats.getTotalBytes());
- tagFuture = expectEnqueueNotification();
+ history.removeBucketsBefore(Long.MAX_VALUE);
+ history.recordData(start, end,
+ new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
- mNetworkObserver.limitReached(null, TEST_IFACE);
+ reset(mTelephonyManager, mNetworkManager, mNotifManager);
+ expectMobileDefaults();
- assertNotificationType(TYPE_WARNING, tagFuture.get());
- verifyPolicyDataEnable(TYPE_WIFI, true);
+ mService.updateNetworks();
+ verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+ DataUnit.MEGABYTES.toBytes(1800 - 1799));
+ verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_WARNING),
+ isA(Notification.class), eq(UserHandle.ALL));
}
- // go over limit, which should kick notification and dialog
- incrementCurrentTime(MINUTE_IN_MILLIS);
- stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 5 * MB_IN_BYTES, 512L, 0L, 0L);
-
+ // Push over limit
{
- expectCurrentTime();
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START,
- CYCLE_END)).thenReturn(stats.getTotalBytes());
- tagFuture = expectEnqueueNotification();
+ history.removeBucketsBefore(Long.MAX_VALUE);
+ history.recordData(start, end,
+ new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
- mNetworkObserver.limitReached(null, TEST_IFACE);
+ reset(mTelephonyManager, mNetworkManager, mNotifManager);
+ expectMobileDefaults();
- assertNotificationType(TYPE_LIMIT, tagFuture.get());
- verifyPolicyDataEnable(TYPE_WIFI, false);
- }
+ mService.updateNetworks();
- // now snooze policy, which should remove quota
- incrementCurrentTime(MINUTE_IN_MILLIS);
+ verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(false, TEST_SUB_ID);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE, 1);
+ verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT),
+ isA(Notification.class), eq(UserHandle.ALL));
+ }
+ // Snooze limit
{
- expectCurrentTime();
- when(mConnManager.getAllNetworkState()).thenReturn(state);
- when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START,
- CYCLE_END)).thenReturn(stats.getTotalBytes());
- tagFuture = expectEnqueueNotification();
+ reset(mTelephonyManager, mNetworkManager, mNotifManager);
+ expectMobileDefaults();
- mPolicyListener.expect().onMeteredIfacesChanged(any());
- mService.snoozeLimit(sTemplateWifi);
- mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+ mService.snoozeLimit(NetworkTemplate.buildTemplateMobileAll(TEST_IMSI));
+ mService.updateNetworks();
- assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get());
- // snoozed interface still has high quota so background data is
- // still restricted.
- verifyRemoveInterfaceQuota(TEST_IFACE);
- verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
- verifyPolicyDataEnable(TYPE_WIFI, true);
+ verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+ Long.MAX_VALUE);
+ verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT_SNOOZED),
+ isA(Notification.class), eq(UserHandle.ALL));
}
}
@Test
- public void testRapidNotification() throws Exception {
+ public void testNotificationRapid() throws Exception {
// Create a place to store fake usage
final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
}
});
+ // Get active mobile network in place
+ expectMobileDefaults();
+ mService.updateNetworks();
+
// 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)
+ .setDataLimit(DataUnit.MEGABYTES.toBytes(1800), LIMIT_BEHAVIOR_DISABLED)
.build();
- mService.setSubscriptionPlans(42, new SubscriptionPlan[] { plan },
+ mService.setSubscriptionPlans(TEST_SUB_ID, 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);
+ expectCurrentTime();
- // Using 20% of data in 20% is normal
+ // Using 20% data in 20% time 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());
+ mService.updateNetworks();
+ verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
}
// Using 80% data in 20% time is alarming
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());
+ mService.updateNetworks();
+ verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_RAPID),
+ isA(Notification.class), eq(UserHandle.ALL));
}
}
true));
mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
- verifyPolicyDataEnable(TYPE_WIFI, true);
- verifyRemoveInterfaceQuota(TEST_IFACE);
- verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
+ verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
+ Long.MAX_VALUE);
}
}
true);
}
+ private NetworkInfo buildNetworkInfo() {
+ final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
+ TelephonyManager.NETWORK_TYPE_LTE, null, null);
+ ni.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+ return ni;
+ }
+
+ private LinkProperties buildLinkProperties(String iface) {
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(iface);
+ return lp;
+ }
+
+ private NetworkCapabilities buildNetworkCapabilities(int subId) {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_CELLULAR);
+ nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+ return nc;
+ }
+
private NetworkPolicy buildDefaultFakeMobilePolicy() {
NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
// set a deterministic cycle date
when(mTime.getCacheCertainty()).thenReturn(0L);
}
- private Future<String> expectEnqueueNotification() throws Exception {
- final FutureAnswer<String> futureAnswer = new FutureAnswer<String>(2);
- doAnswer(futureAnswer).when(mNotifManager).enqueueNotificationWithTag(
- anyString(), anyString(), anyString() /* capture here (index 2)*/,
- anyInt(), isA(Notification.class), anyInt());
- return futureAnswer;
- }
-
private void expectHasInternetPermission(int uid, boolean hasIt) throws Exception {
when(mIpm.checkUidPermission(Manifest.permission.INTERNET, uid)).thenReturn(
hasIt ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
}
- private void verifySetInterfaceQuota(String iface, long quotaBytes) throws Exception {
- verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(iface, quotaBytes);
- }
-
- private void verifyRemoveInterfaceQuota(String iface) throws Exception {
- verify(mNetworkManager, atLeastOnce()).removeInterfaceQuota(iface);
- }
-
- private Future<Void> verifyPolicyDataEnable(int type, boolean enabled) throws Exception {
- // TODO: bring back this test
- return null;
+ private void expectMobileDefaults() throws Exception {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
+ new int[] { TEST_SUB_ID });
+ when(mTelephonyManager.getSubscriberId(TEST_SUB_ID)).thenReturn(TEST_IMSI);
+ when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[] {
+ new NetworkState(buildNetworkInfo(),
+ buildLinkProperties(TEST_IFACE),
+ buildNetworkCapabilities(TEST_SUB_ID),
+ new Network(TEST_NET_ID), TEST_IMSI, null)
+ });
}
private void verifyAdvisePersistThreshold() throws Exception {
}
}
- private static class FutureAnswer<T> extends TestAbstractFuture<T> implements Answer<Void> {
- private final int index;
-
- FutureAnswer(int index) {
- this.index = index;
- }
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- @SuppressWarnings("unchecked")
- T captured = (T) invocation.getArguments()[index];
- set(captured);
- return null;
- }
- }
-
private static void assertTimeEquals(long expected, long actual) {
if (expected != actual) {
fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual));