summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2011-07-25 15:21:22 -0700
committerJeff Sharkey <jsharkey@android.com>2011-07-27 09:31:39 -0700
commit41ff7ec82422a5b6d00892afdb3232bc0e53d851 (patch)
tree9085ebb6bcc39a20f98eeb71860f2dad01f604ed
parenta94b9ad23ac1f281c9d2dac02d01aa07ca5e1682 (diff)
downloadframeworks_base-41ff7ec82422a5b6d00892afdb3232bc0e53d851.zip
frameworks_base-41ff7ec82422a5b6d00892afdb3232bc0e53d851.tar.gz
frameworks_base-41ff7ec82422a5b6d00892afdb3232bc0e53d851.tar.bz2
Revise data limit notifs, watch kernel alerts.
Teach NetworkPolicy limits to "snooze" when requested by user, and notify with both dialog and notification. Register for network alerts through NMS to trigger updates immediately instead of waiting for next stats update. Enforce that all NetworkPolicy are unique on a template basis, and move SCREEN_ON/OFF broadcasts to background thread. Launch SystemUI and Settings directly instead of using actions, and include full NetworkTemplate in extras. Tests to verify notification and snooze behavior. Bug: 5057979, 5023579, 4723336, 5045721 Change-Id: I03724beff94a7c0547cb5220431ba8d4cd44d077
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl3
-rw-r--r--core/java/android/net/NetworkPolicy.java31
-rw-r--r--core/java/android/net/NetworkPolicyManager.java22
-rw-r--r--core/java/android/os/INetworkManagementService.aidl17
-rwxr-xr-xcore/res/res/values/strings.xml9
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/res/values/strings.xml13
-rw-r--r--packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java98
-rw-r--r--services/java/com/android/server/NetworkManagementService.java107
-rw-r--r--services/java/com/android/server/net/NetworkAlertObserver.java44
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java315
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java211
13 files changed, 771 insertions, 112 deletions
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 82495e3..6fde746 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -18,6 +18,7 @@ package android.net;
import android.net.INetworkPolicyListener;
import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
/**
* Interface that creates and modifies network policy rules.
@@ -37,4 +38,6 @@ interface INetworkPolicyManager {
void setNetworkPolicies(in NetworkPolicy[] policies);
NetworkPolicy[] getNetworkPolicies();
+ void snoozePolicy(in NetworkTemplate template);
+
}
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 52cab30..aaad8a1 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -21,6 +21,8 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Objects;
+
/**
* Policy for networks matching a {@link NetworkTemplate}, including usage cycle
* and limits to be enforced.
@@ -30,20 +32,21 @@ import android.os.Parcelable;
public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
public static final long WARNING_DISABLED = -1;
public static final long LIMIT_DISABLED = -1;
+ public static final long SNOOZE_NEVER = -1;
public final NetworkTemplate template;
public int cycleDay;
public long warningBytes;
public long limitBytes;
+ public long lastSnooze;
- // TODO: teach how to snooze limit for current cycle
-
- public NetworkPolicy(
- NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes) {
+ public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes,
+ long lastSnooze) {
this.template = checkNotNull(template, "missing NetworkTemplate");
this.cycleDay = cycleDay;
this.warningBytes = warningBytes;
this.limitBytes = limitBytes;
+ this.lastSnooze = lastSnooze;
}
public NetworkPolicy(Parcel in) {
@@ -51,6 +54,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
cycleDay = in.readInt();
warningBytes = in.readLong();
limitBytes = in.readLong();
+ lastSnooze = in.readLong();
}
/** {@inheritDoc} */
@@ -59,6 +63,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
dest.writeInt(cycleDay);
dest.writeLong(warningBytes);
dest.writeLong(limitBytes);
+ dest.writeLong(lastSnooze);
}
/** {@inheritDoc} */
@@ -80,9 +85,25 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
}
@Override
+ public int hashCode() {
+ return Objects.hashCode(template, cycleDay, warningBytes, limitBytes, lastSnooze);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkPolicy) {
+ final NetworkPolicy other = (NetworkPolicy) obj;
+ return Objects.equal(template, other.template) && cycleDay == other.cycleDay
+ && warningBytes == other.warningBytes && limitBytes == other.limitBytes
+ && lastSnooze == other.lastSnooze;
+ }
+ return false;
+ }
+
+ @Override
public String toString() {
return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", warningBytes="
- + warningBytes + ", limitBytes=" + limitBytes;
+ + warningBytes + ", limitBytes=" + limitBytes + ", lastSnooze=" + lastSnooze;
}
public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 593b2b7..1e9d813 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -52,26 +52,10 @@ public class NetworkPolicyManager {
private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
/**
- * {@link Intent} action launched when user selects {@link NetworkPolicy}
- * warning notification.
+ * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
+ * applies to.
*/
- public static final String ACTION_DATA_USAGE_WARNING =
- "android.intent.action.DATA_USAGE_WARNING";
-
- /**
- * {@link Intent} action launched when user selects {@link NetworkPolicy}
- * limit notification.
- */
- public static final String ACTION_DATA_USAGE_LIMIT =
- "android.intent.action.DATA_USAGE_LIMIT";
-
- /**
- * {@link Intent} extra included in {@link #ACTION_DATA_USAGE_WARNING} and
- * {@link #ACTION_DATA_USAGE_LIMIT} to indicate which
- * {@link NetworkTemplate} rule it applies to.
- */
- public static final String EXTRA_NETWORK_TEMPLATE =
- "android.intent.extra.NETWORK_TEMPLATE";
+ public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE";
private INetworkPolicyManager mService;
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 03a6c07..3704248 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -212,7 +212,7 @@ interface INetworkManagementService
/**
* Set quota for an interface.
*/
- void setInterfaceQuota(String iface, long quota);
+ void setInterfaceQuota(String iface, long quotaBytes);
/**
* Remove quota for an interface.
@@ -220,6 +220,21 @@ interface INetworkManagementService
void removeInterfaceQuota(String iface);
/**
+ * Set alert for an interface; requires that iface already has quota.
+ */
+ void setInterfaceAlert(String iface, long alertBytes);
+
+ /**
+ * Remove alert for an interface.
+ */
+ void removeInterfaceAlert(String iface);
+
+ /**
+ * Set alert across all interfaces.
+ */
+ void setGlobalAlert(long alertBytes);
+
+ /**
* Control network activity of a UID over interfaces with a quota limit.
*/
void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e1a31f4..d698341 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3034,6 +3034,15 @@
<!-- Notification body when data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
<string name="data_usage_limit_body">tap to enable</string>
+ <!-- Notification title when 2G-3G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_3g_limit_snoozed_title">2G-3G data limit exceeded</string>
+ <!-- Notification title when 4G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
+ <!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
+ <!-- Notification body when data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_limit_snoozed_body"><xliff:g id="size" example="3.8GB">%s</xliff:g> over specified limit</string>
+
<!-- SSL Certificate dialogs -->
<!-- Title for an SSL Certificate dialog -->
<string name="ssl_certificate">Security certificate</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d32df6e..ba9b5b0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -82,5 +82,17 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true">
</activity>
+
+ <!-- started from NetworkPolicyManagerService -->
+ <activity
+ android:name=".net.NetworkOverLimitActivity"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_NETWORK_POLICY"
+ android:theme="@android:style/Theme.Holo.Panel"
+ android:finishOnCloseSystemDialogs="true"
+ android:launchMode="singleTop"
+ android:taskAffinity="com.android.systemui.net"
+ android:excludeFromRecents="true" />
+
</application>
</manifest>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1b60b16..5ca77fc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -300,6 +300,19 @@
<!-- Content description of the ringer silent icon in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_ringer_silent">Ringer silent.</string>
+ <!-- Title of dialog shown when 2G-3G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
+ <string name="data_usage_disabled_dialog_3g_title">2G-3G data disabled</string>
+ <!-- Title of dialog shown when 4G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
+ <string name="data_usage_disabled_dialog_4g_title">4G data disabled</string>
+ <!-- Title of dialog shown when mobile data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
+ <string name="data_usage_disabled_dialog_mobile_title">Mobile data disabled</string>
+ <!-- Title of dialog shown when data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] -->
+ <string name="data_usage_disabled_dialog_title">Data disabled</string>
+ <!-- Body of dialog shown when data usage has exceeded limit and has been disabled. [CHAR LIMIT=NONE] -->
+ <string name="data_usage_disabled_dialog">The specified data usage limit has been reached.\n\nAdditional data use may incur carrier charges.</string>
+ <!-- Dialog button indicating that data connection should be re-enabled. [CHAR LIMIT=28] -->
+ <string name="data_usage_disabled_dialog_enable">Re-enable data</string>
+
<!-- Text to display underneath the graphical signal strength meter when
no connection is available. [CHAR LIMIT=20] -->
<string name="status_bar_settings_signal_meter_disconnected">
diff --git a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
new file mode 100644
index 0000000..723e338
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.net;
+
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+/**
+ * Notify user that a {@link NetworkTemplate} is over its
+ * {@link NetworkPolicy#limitBytes}, giving them the choice of acknowledging or
+ * "snoozing" the limit.
+ */
+public class NetworkOverLimitActivity extends Activity {
+ private static final String TAG = "NetworkOverLimitActivity";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final NetworkTemplate template = getIntent().getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getLimitedDialogTitleForTemplate(template));
+ builder.setMessage(R.string.data_usage_disabled_dialog);
+
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(
+ R.string.data_usage_disabled_dialog_enable, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ snoozePolicy(template);
+ }
+ });
+
+ final Dialog dialog = builder.create();
+ dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+ });
+
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void snoozePolicy(NetworkTemplate template) {
+ final INetworkPolicyManager policyService = INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ try {
+ policyService.snoozePolicy(template);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem snoozing network policy", e);
+ }
+ }
+
+ private static int getLimitedDialogTitleForTemplate(NetworkTemplate template) {
+ switch (template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ return R.string.data_usage_disabled_dialog_3g_title;
+ case MATCH_MOBILE_4G:
+ return R.string.data_usage_disabled_dialog_4g_title;
+ case MATCH_MOBILE_ALL:
+ return R.string.data_usage_disabled_dialog_mobile_title;
+ default:
+ return R.string.data_usage_disabled_dialog_title;
+ }
+ }
+}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index a16f748..39d2b1c 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -125,10 +125,14 @@ class NetworkManagementService extends INetworkManagementService.Stub {
private Thread mThread;
private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
+ // TODO: replace with RemoteCallbackList
private ArrayList<INetworkManagementEventObserver> mObservers;
+ private Object mQuotaLock = new Object();
/** Set of interfaces with active quotas. */
- private HashSet<String> mInterfaceQuota = Sets.newHashSet();
+ private HashSet<String> mActiveQuotaIfaces = Sets.newHashSet();
+ /** Set of interfaces with active alerts. */
+ private HashSet<String> mActiveAlertIfaces = Sets.newHashSet();
/** Set of UIDs with active reject rules. */
private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
@@ -1058,26 +1062,25 @@ class NetworkManagementService extends INetworkManagementService.Stub {
}
@Override
- public void setInterfaceQuota(String iface, long quota) {
+ public void setInterfaceQuota(String iface, long quotaBytes) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
// silently discard when control disabled
// TODO: eventually migrate to be always enabled
if (!mBandwidthControlEnabled) return;
- synchronized (mInterfaceQuota) {
- if (mInterfaceQuota.contains(iface)) {
- // TODO: eventually consider throwing
- return;
+ synchronized (mQuotaLock) {
+ if (mActiveQuotaIfaces.contains(iface)) {
+ throw new IllegalStateException("iface " + iface + " already has quota");
}
final StringBuilder command = new StringBuilder();
- command.append("bandwidth setiquota ").append(iface).append(" ").append(quota);
+ command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes);
try {
- // TODO: add support for quota shared across interfaces
+ // TODO: support quota shared across interfaces
mConnector.doCommand(command.toString());
- mInterfaceQuota.add(iface);
+ mActiveQuotaIfaces.add(iface);
} catch (NativeDaemonConnectorException e) {
throw new IllegalStateException("Error communicating to native daemon", e);
}
@@ -1092,8 +1095,8 @@ class NetworkManagementService extends INetworkManagementService.Stub {
// TODO: eventually migrate to be always enabled
if (!mBandwidthControlEnabled) return;
- synchronized (mInterfaceQuota) {
- if (!mInterfaceQuota.contains(iface)) {
+ synchronized (mQuotaLock) {
+ if (!mActiveQuotaIfaces.contains(iface)) {
// TODO: eventually consider throwing
return;
}
@@ -1102,9 +1105,10 @@ class NetworkManagementService extends INetworkManagementService.Stub {
command.append("bandwidth removeiquota ").append(iface);
try {
- // TODO: add support for quota shared across interfaces
+ // TODO: support quota shared across interfaces
mConnector.doCommand(command.toString());
- mInterfaceQuota.remove(iface);
+ mActiveQuotaIfaces.remove(iface);
+ mActiveAlertIfaces.remove(iface);
} catch (NativeDaemonConnectorException e) {
throw new IllegalStateException("Error communicating to native daemon", e);
}
@@ -1112,6 +1116,83 @@ class NetworkManagementService extends INetworkManagementService.Stub {
}
@Override
+ public void setInterfaceAlert(String iface, long alertBytes) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ // silently discard when control disabled
+ // TODO: eventually migrate to be always enabled
+ if (!mBandwidthControlEnabled) return;
+
+ // quick sanity check
+ if (!mActiveQuotaIfaces.contains(iface)) {
+ throw new IllegalStateException("setting alert requires existing quota on iface");
+ }
+
+ synchronized (mQuotaLock) {
+ if (mActiveAlertIfaces.contains(iface)) {
+ throw new IllegalStateException("iface " + iface + " already has alert");
+ }
+
+ final StringBuilder command = new StringBuilder();
+ command.append("bandwidth setinterfacealert ").append(iface).append(" ").append(
+ alertBytes);
+
+ try {
+ // TODO: support alert shared across interfaces
+ mConnector.doCommand(command.toString());
+ mActiveAlertIfaces.add(iface);
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException("Error communicating to native daemon", e);
+ }
+ }
+ }
+
+ @Override
+ public void removeInterfaceAlert(String iface) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ // silently discard when control disabled
+ // TODO: eventually migrate to be always enabled
+ if (!mBandwidthControlEnabled) return;
+
+ synchronized (mQuotaLock) {
+ if (!mActiveAlertIfaces.contains(iface)) {
+ // TODO: eventually consider throwing
+ return;
+ }
+
+ final StringBuilder command = new StringBuilder();
+ command.append("bandwidth removeinterfacealert ").append(iface);
+
+ try {
+ // TODO: support alert shared across interfaces
+ mConnector.doCommand(command.toString());
+ mActiveAlertIfaces.remove(iface);
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException("Error communicating to native daemon", e);
+ }
+ }
+ }
+
+ @Override
+ public void setGlobalAlert(long alertBytes) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ // silently discard when control disabled
+ // TODO: eventually migrate to be always enabled
+ if (!mBandwidthControlEnabled) return;
+
+ final StringBuilder command = new StringBuilder();
+ command.append("bandwidth setglobalalert ").append(alertBytes);
+
+ try {
+ mConnector.doCommand(command.toString());
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException("Error communicating to native daemon", e);
+ }
+ }
+
+ @Override
public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
diff --git a/services/java/com/android/server/net/NetworkAlertObserver.java b/services/java/com/android/server/net/NetworkAlertObserver.java
new file mode 100644
index 0000000..0d1c3b2
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkAlertObserver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.INetworkManagementEventObserver;
+
+/**
+ * @hide
+ */
+public abstract class NetworkAlertObserver extends INetworkManagementEventObserver.Stub {
+ @Override
+ public void interfaceStatusChanged(String iface, boolean up) {
+ // ignored; interface changes come through ConnectivityService
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ // ignored; interface changes come through ConnectivityService
+ }
+
+ @Override
+ public void interfaceLinkStateChanged(String iface, boolean up) {
+ // ignored; interface changes come through ConnectivityService
+ }
+
+ @Override
+ public void interfaceAdded(String iface) {
+ // ignored; interface changes come through ConnectivityService
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 435c394..2e1e69b 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -29,9 +29,8 @@ import static android.net.ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHA
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
-import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_WARNING;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -56,6 +55,7 @@ import android.app.IProcessObserver;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -64,6 +64,7 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
@@ -131,14 +132,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final boolean LOGD = true;
private static final boolean LOGV = false;
- private static final int VERSION_CURRENT = 1;
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADDED_SNOOZE = 2;
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
- private static final int TYPE_WARNING = 0x1;
- private static final int TYPE_LIMIT = 0x2;
+ // @VisibleForTesting
+ public static final int TYPE_WARNING = 0x1;
+ public static final int TYPE_LIMIT = 0x2;
+ public static final int TYPE_LIMIT_SNOOZED = 0x3;
private static final String TAG_POLICY_LIST = "policy-list";
private static final String TAG_NETWORK_POLICY = "network-policy";
@@ -150,6 +154,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String ATTR_CYCLE_DAY = "cycleDay";
private static final String ATTR_WARNING_BYTES = "warningBytes";
private static final String ATTR_LIMIT_BYTES = "limitBytes";
+ private static final String ATTR_LAST_SNOOZE = "lastSnooze";
private static final String ATTR_UID = "uid";
private static final String ATTR_POLICY = "policy";
@@ -162,7 +167,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final IActivityManager mActivityManager;
private final IPowerManager mPowerManager;
private final INetworkStatsService mNetworkStats;
- private final INetworkManagementService mNetworkManagement;
+ private final INetworkManagementService mNetworkManager;
private final TrustedTime mTime;
private IConnectivityManager mConnManager;
@@ -173,18 +178,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private boolean mScreenOn;
private boolean mBackgroundData;
- /** Current policy for network templates. */
- private ArrayList<NetworkPolicy> mNetworkPolicy = Lists.newArrayList();
- /** Current derived network rules for ifaces. */
+ /** Defined network policies. */
+ private HashMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = Maps.newHashMap();
+ /** Currently active network rules for ifaces. */
private HashMap<NetworkPolicy, String[]> mNetworkRules = Maps.newHashMap();
- /** Current policy for each UID. */
+ /** Defined UID policies. */
private SparseIntArray mUidPolicy = new SparseIntArray();
- /** Current derived rules for each UID. */
+ /** Currently derived rules for each UID. */
private SparseIntArray mUidRules = new SparseIntArray();
/** Set of ifaces that are metered. */
private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+ /** Set of over-limit templates that have been notified. */
+ private HashSet<NetworkTemplate> mOverLimitNotified = Sets.newHashSet();
/** Foreground at both UID and PID granularity. */
private SparseBooleanArray mUidForeground = new SparseBooleanArray();
@@ -202,6 +209,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
+ // TODO: migrate notifications to SystemUI
+
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
IPowerManager powerManager, INetworkStatsService networkStats,
INetworkManagementService networkManagement) {
@@ -221,7 +230,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
mNetworkStats = checkNotNull(networkStats, "missing networkStats");
- mNetworkManagement = checkNotNull(networkManagement, "missing networkManagement");
+ mNetworkManager = checkNotNull(networkManagement, "missing networkManagement");
mTime = checkNotNull(time, "missing TrustedTime");
mHandlerThread = new HandlerThread(TAG);
@@ -256,13 +265,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
Slog.e(TAG, "unable to register IProcessObserver", e);
}
+ try {
+ mNetworkManager.registerObserver(mAlertObserver);
+ } catch (RemoteException e) {
+ // ouch, no alert updates means we fall back to
+ // ACTION_NETWORK_STATS_UPDATED broadcasts.
+ Slog.e(TAG, "unable to register INetworkManagementEventObserver", e);
+ }
+
// TODO: traverse existing processes to know foreground state, or have
// activitymanager dispatch current state when new observer attached.
final IntentFilter screenFilter = new IntentFilter();
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mScreenReceiver, screenFilter);
+ mContext.registerReceiver(mScreenReceiver, screenFilter, null, mHandler);
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -272,7 +289,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
packageFilter.addAction(ACTION_UID_REMOVED);
- packageFilter.addDataScheme("package");
mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler);
// listen for stats update events
@@ -393,6 +409,31 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
};
/**
+ * Observer that watches for {@link INetworkManagementService} alerts.
+ */
+ private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() {
+ @Override
+ public void limitReached(String limitName, String iface) {
+ // only someone like NMS should be calling us
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ synchronized (mRulesLock) {
+ if (mMeteredIfaces.contains(iface)) {
+ try {
+ // force stats update to make sure we have numbers that
+ // caused alert to trigger.
+ mNetworkStats.forceUpdate();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem updating network stats");
+ }
+
+ updateNotificationsLocked();
+ }
+ }
+ }
+ };
+
+ /**
* Check {@link NetworkPolicy} against current {@link INetworkStatsService}
* to show visible notifications as needed.
*/
@@ -415,42 +456,69 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final long start = computeLastCycleBoundary(currentTime, policy);
final long end = currentTime;
- final long total;
+ final long totalBytes;
try {
final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
policy.template, start, end);
final NetworkStats.Entry entry = stats.getValues(0, null);
- total = entry.rxBytes + entry.txBytes;
+ totalBytes = entry.rxBytes + entry.txBytes;
} catch (RemoteException e) {
Slog.w(TAG, "problem reading summary for template " + policy.template);
continue;
}
- if (policy.limitBytes != LIMIT_DISABLED && total >= policy.limitBytes) {
+ if (policy.limitBytes != LIMIT_DISABLED && totalBytes >= policy.limitBytes) {
cancelNotification(policy, TYPE_WARNING);
- enqueueNotification(policy, TYPE_LIMIT);
+
+ if (policy.lastSnooze >= start) {
+ cancelNotification(policy, TYPE_LIMIT);
+ enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
+ } else {
+ cancelNotification(policy, TYPE_LIMIT_SNOOZED);
+ enqueueNotification(policy, TYPE_LIMIT, totalBytes);
+ notifyOverLimitLocked(policy.template);
+ }
+
} else {
cancelNotification(policy, TYPE_LIMIT);
+ cancelNotification(policy, TYPE_LIMIT_SNOOZED);
+ notifyUnderLimitLocked(policy.template);
- if (policy.warningBytes != WARNING_DISABLED && total >= policy.warningBytes) {
- enqueueNotification(policy, TYPE_WARNING);
+ if (policy.warningBytes != WARNING_DISABLED && totalBytes >= policy.warningBytes) {
+ enqueueNotification(policy, TYPE_WARNING, totalBytes);
} else {
cancelNotification(policy, TYPE_WARNING);
}
}
-
}
// clear notifications for non-active policies
- for (NetworkPolicy policy : mNetworkPolicy) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
if (!mNetworkRules.containsKey(policy)) {
cancelNotification(policy, TYPE_WARNING);
cancelNotification(policy, TYPE_LIMIT);
+ cancelNotification(policy, TYPE_LIMIT_SNOOZED);
+ notifyUnderLimitLocked(policy.template);
}
}
}
/**
+ * Notify that given {@link NetworkTemplate} is over
+ * {@link NetworkPolicy#limitBytes}, potentially showing dialog to user.
+ */
+ private void notifyOverLimitLocked(NetworkTemplate template) {
+ if (!mOverLimitNotified.contains(template)) {
+ mContext.startActivity(buildNetworkOverLimitIntent(template));
+ mOverLimitNotified.add(template);
+ }
+ }
+
+ private void notifyUnderLimitLocked(NetworkTemplate template) {
+ mOverLimitNotified.remove(template);
+ }
+
+ /**
* Build unique tag that identifies an active {@link NetworkPolicy}
* notification of a specific type, like {@link #TYPE_LIMIT}.
*/
@@ -462,7 +530,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* Show notification for combined {@link NetworkPolicy} and specific type,
* like {@link #TYPE_LIMIT}. Okay to call multiple times.
*/
- private void enqueueNotification(NetworkPolicy policy, int type) {
+ private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes) {
final String tag = buildNotificationTag(policy, type);
final Notification.Builder builder = new Notification.Builder(mContext);
builder.setOnlyAlertOnce(true);
@@ -471,8 +539,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final Resources res = mContext.getResources();
switch (type) {
case TYPE_WARNING: {
- final String title = res.getString(R.string.data_usage_warning_title);
- final String body = res.getString(R.string.data_usage_warning_body,
+ final CharSequence title = res.getText(R.string.data_usage_warning_title);
+ final CharSequence body = res.getString(R.string.data_usage_warning_body,
Formatter.formatFileSize(mContext, policy.warningBytes));
builder.setSmallIcon(R.drawable.ic_menu_info_details);
@@ -480,25 +548,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
builder.setContentTitle(title);
builder.setContentText(body);
- final Intent intent = new Intent(ACTION_DATA_USAGE_WARNING);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
+ final Intent intent = buildViewDataUsageIntent(policy.template);
builder.setContentIntent(PendingIntent.getActivity(
mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
break;
}
case TYPE_LIMIT: {
- final String title;
- final String body = res.getString(R.string.data_usage_limit_body);
+ final CharSequence body = res.getText(R.string.data_usage_limit_body);
+
+ final CharSequence title;
switch (policy.template.getMatchRule()) {
case MATCH_MOBILE_3G_LOWER:
- title = res.getString(R.string.data_usage_3g_limit_title);
+ title = res.getText(R.string.data_usage_3g_limit_title);
break;
case MATCH_MOBILE_4G:
- title = res.getString(R.string.data_usage_4g_limit_title);
+ title = res.getText(R.string.data_usage_4g_limit_title);
break;
default:
- title = res.getString(R.string.data_usage_mobile_limit_title);
+ title = res.getText(R.string.data_usage_mobile_limit_title);
break;
}
@@ -507,9 +574,35 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
builder.setContentTitle(title);
builder.setContentText(body);
- final Intent intent = new Intent(ACTION_DATA_USAGE_LIMIT);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
+ final Intent intent = buildNetworkOverLimitIntent(policy.template);
+ builder.setContentIntent(PendingIntent.getActivity(
+ mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+ break;
+ }
+ case TYPE_LIMIT_SNOOZED: {
+ final long overBytes = totalBytes - policy.limitBytes;
+ final CharSequence body = res.getString(R.string.data_usage_limit_snoozed_body,
+ Formatter.formatFileSize(mContext, overBytes));
+
+ final CharSequence title;
+ switch (policy.template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ title = res.getText(R.string.data_usage_3g_limit_snoozed_title);
+ break;
+ case MATCH_MOBILE_4G:
+ title = res.getText(R.string.data_usage_4g_limit_snoozed_title);
+ break;
+ default:
+ title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
+ break;
+ }
+
+ builder.setSmallIcon(R.drawable.ic_menu_info_details);
+ builder.setTicker(title);
+ builder.setContentTitle(title);
+ builder.setContentText(body);
+
+ final Intent intent = buildViewDataUsageIntent(policy.template);
builder.setContentIntent(PendingIntent.getActivity(
mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
break;
@@ -591,7 +684,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// build list of rules and ifaces to enforce them against
mNetworkRules.clear();
final ArrayList<String> ifaceList = Lists.newArrayList();
- for (NetworkPolicy policy : mNetworkPolicy) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
// collect all active ifaces that match this template
ifaceList.clear();
@@ -642,11 +735,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
+ Arrays.toString(ifaces));
}
- // TODO: register for warning notification trigger through NMS
+ final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
+ final boolean hasWarning = policy.warningBytes != WARNING_DISABLED;
- if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
- // remaining "quota" is based on usage in current cycle
- final long quota = Math.max(0, policy.limitBytes - total);
+ if (hasLimit || hasWarning) {
+ final long quotaBytes;
+ if (hasLimit) {
+ // remaining "quota" is based on usage in current cycle
+ quotaBytes = Math.max(0, policy.limitBytes - total);
+ } else {
+ // to track warning alert later, use a high quota
+ quotaBytes = Long.MAX_VALUE;
+ }
if (ifaces.length > 1) {
// TODO: switch to shared quota once NMS supports
@@ -655,8 +755,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
for (String iface : ifaces) {
removeInterfaceQuota(iface);
- setInterfaceQuota(iface, quota);
- newMeteredIfaces.add(iface);
+ if (quotaBytes > 0) {
+ setInterfaceQuota(iface, quotaBytes);
+ newMeteredIfaces.add(iface);
+ }
+ }
+ }
+
+ if (hasWarning) {
+ final long alertBytes = Math.max(0, policy.warningBytes - total);
+ for (String iface : ifaces) {
+ removeInterfaceAlert(iface);
+ if (alertBytes > 0) {
+ setInterfaceAlert(iface, alertBytes);
+ }
}
}
}
@@ -685,7 +797,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// examine to see if any policy is defined for active mobile
boolean mobileDefined = false;
- for (NetworkPolicy policy : mNetworkPolicy) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
if (policy.template.matches(probeIdent)) {
mobileDefined = true;
}
@@ -704,7 +816,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int cycleDay = time.monthDay;
final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
- mNetworkPolicy.add(new NetworkPolicy(template, cycleDay, warningBytes, LIMIT_DISABLED));
+ mNetworkPolicy.put(template, new NetworkPolicy(
+ template, cycleDay, warningBytes, LIMIT_DISABLED, SNOOZE_NEVER));
writePolicyLocked();
}
}
@@ -723,7 +836,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
in.setInput(fis, null);
int type;
- int version = VERSION_CURRENT;
+ int version = VERSION_INIT;
while ((type = in.next()) != END_DOCUMENT) {
final String tag = in.getName();
if (type == START_TAG) {
@@ -736,11 +849,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
+ final long lastSnooze;
+ if (version >= VERSION_ADDED_SNOOZE) {
+ lastSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE);
+ } else {
+ lastSnooze = SNOOZE_NEVER;
+ }
final NetworkTemplate template = new NetworkTemplate(
networkTemplate, subscriberId);
- mNetworkPolicy.add(
- new NetworkPolicy(template, cycleDay, warningBytes, limitBytes));
+ mNetworkPolicy.put(template, new NetworkPolicy(
+ template, cycleDay, warningBytes, limitBytes, lastSnooze));
} else if (TAG_UID_POLICY.equals(tag)) {
final int uid = readIntAttribute(in, ATTR_UID);
@@ -778,10 +897,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
out.startDocument(null, true);
out.startTag(null, TAG_POLICY_LIST);
- writeIntAttribute(out, ATTR_VERSION, VERSION_CURRENT);
+ writeIntAttribute(out, ATTR_VERSION, VERSION_ADDED_SNOOZE);
// write all known network policies
- for (NetworkPolicy policy : mNetworkPolicy) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
final NetworkTemplate template = policy.template;
out.startTag(null, TAG_NETWORK_POLICY);
@@ -793,6 +912,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes);
+ writeLongAttribute(out, ATTR_LAST_SNOOZE, policy.lastSnooze);
out.endTag(null, TAG_NETWORK_POLICY);
}
@@ -880,7 +1000,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
synchronized (mRulesLock) {
mNetworkPolicy.clear();
for (NetworkPolicy policy : policies) {
- mNetworkPolicy.add(policy);
+ mNetworkPolicy.put(policy.template, policy);
}
updateNetworkRulesLocked();
@@ -895,7 +1015,34 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG);
synchronized (mRulesLock) {
- return mNetworkPolicy.toArray(new NetworkPolicy[mNetworkPolicy.size()]);
+ return mNetworkPolicy.values().toArray(new NetworkPolicy[mNetworkPolicy.size()]);
+ }
+ }
+
+ @Override
+ public void snoozePolicy(NetworkTemplate template) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ synchronized (mRulesLock) {
+ // find and snooze local policy that matches
+ final NetworkPolicy policy = mNetworkPolicy.get(template);
+ if (policy == null) {
+ throw new IllegalArgumentException("unable to find policy for " + template);
+ }
+
+ policy.lastSnooze = currentTime;
+
+ updateNetworkRulesLocked();
+ updateNotificationsLocked();
+ writePolicyLocked();
}
}
@@ -903,9 +1050,23 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+ final HashSet<String> argSet = new HashSet<String>();
+ for (String arg : args) {
+ argSet.add(arg);
+ }
+
synchronized (mRulesLock) {
+ if (argSet.contains("unsnooze")) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
+ policy.lastSnooze = SNOOZE_NEVER;
+ }
+ writePolicyLocked();
+ fout.println("Wiped snooze timestamps");
+ return;
+ }
+
fout.println("Network policies:");
- for (NetworkPolicy policy : mNetworkPolicy) {
+ for (NetworkPolicy policy : mNetworkPolicy.values()) {
fout.print(" "); fout.println(policy.toString());
}
@@ -1124,9 +1285,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
- private void setInterfaceQuota(String iface, long quota) {
+ private void setInterfaceQuota(String iface, long quotaBytes) {
try {
- mNetworkManagement.setInterfaceQuota(iface, quota);
+ mNetworkManager.setInterfaceQuota(iface, quotaBytes);
} catch (IllegalStateException e) {
Slog.e(TAG, "problem setting interface quota", e);
} catch (RemoteException e) {
@@ -1136,7 +1297,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private void removeInterfaceQuota(String iface) {
try {
- mNetworkManagement.removeInterfaceQuota(iface);
+ mNetworkManager.removeInterfaceQuota(iface);
} catch (IllegalStateException e) {
Slog.e(TAG, "problem removing interface quota", e);
} catch (RemoteException e) {
@@ -1144,9 +1305,29 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ private void setInterfaceAlert(String iface, long alertBytes) {
+ try {
+ mNetworkManager.setInterfaceAlert(iface, alertBytes);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "problem setting interface alert", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "problem setting interface alert", e);
+ }
+ }
+
+ private void removeInterfaceAlert(String iface) {
+ try {
+ mNetworkManager.removeInterfaceAlert(iface);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "problem removing interface alert", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "problem removing interface alert", e);
+ }
+ }
+
private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
try {
- mNetworkManagement.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
+ mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
} catch (IllegalStateException e) {
Slog.e(TAG, "problem setting uid rules", e);
} catch (RemoteException e) {
@@ -1160,6 +1341,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return telephony.getSubscriberId();
}
+ private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) {
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName(
+ "com.android.systemui", "com.android.systemui.net.NetworkOverLimitActivity"));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+ return intent;
+ }
+
+ private static Intent buildViewDataUsageIntent(NetworkTemplate template) {
+ final Intent intent = new Intent();
+ intent.setComponent(new ComponentName(
+ "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+ return intent;
+ }
+
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
@@ -1198,7 +1397,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
- throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
+ throw new ProtocolException("problem parsing " + name + "=" + value + " as long");
}
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index ee5f3f5..1620405 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 504ba42..aab09ca 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -27,6 +28,10 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+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_WARNING;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.capture;
@@ -39,12 +44,14 @@ import static org.easymock.EasyMock.isA;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IProcessObserver;
+import android.app.Notification;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
@@ -95,7 +102,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
private IActivityManager mActivityManager;
private IPowerManager mPowerManager;
private INetworkStatsService mStatsService;
- private INetworkManagementService mNetworkManagement;
+ private INetworkManagementService mNetworkManager;
private INetworkPolicyListener mPolicyListener;
private TrustedTime mTime;
private IConnectivityManager mConnManager;
@@ -103,6 +110,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
private NetworkPolicyManagerService mService;
private IProcessObserver mProcessObserver;
+ private INetworkManagementEventObserver mNetworkObserver;
private Binder mStubBinder = new Binder();
@@ -141,6 +149,11 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
}
};
}
+
+ @Override
+ public void startActivity(Intent intent) {
+ // ignored
+ }
};
mPolicyDir = getContext().getFilesDir();
@@ -151,7 +164,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mActivityManager = createMock(IActivityManager.class);
mPowerManager = createMock(IPowerManager.class);
mStatsService = createMock(INetworkStatsService.class);
- mNetworkManagement = createMock(INetworkManagementService.class);
+ mNetworkManager = createMock(INetworkManagementService.class);
mPolicyListener = createMock(INetworkPolicyListener.class);
mTime = createMock(TrustedTime.class);
mConnManager = createMock(IConnectivityManager.class);
@@ -159,7 +172,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mService = new NetworkPolicyManagerService(
mServiceContext, mActivityManager, mPowerManager, mStatsService,
- mNetworkManagement, mTime, mPolicyDir);
+ mNetworkManager, mTime, mPolicyDir);
mService.bindConnectivityManager(mConnManager);
mService.bindNotificationManager(mNotifManager);
@@ -169,11 +182,17 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mService.registerListener(mPolicyListener);
verifyAndReset();
- // catch the registered IProcessObserver during systemReady()
+ // catch IProcessObserver during systemReady()
final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
mActivityManager.registerProcessObserver(capture(processObserver));
expectLastCall().atLeastOnce();
+ // catch INetworkManagementEventObserver during systemReady()
+ final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+ INetworkManagementEventObserver>();
+ mNetworkManager.registerObserver(capture(networkObserver));
+ expectLastCall().atLeastOnce();
+
// expect to answer screen status during systemReady()
expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
expectTime(System.currentTimeMillis());
@@ -186,6 +205,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
verifyAndReset();
mProcessObserver = processObserver.getValue();
+ mNetworkObserver = networkObserver.getValue();
}
@@ -382,7 +402,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z");
- final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 5, 1024L, 1024L);
+ final NetworkPolicy policy = new NetworkPolicy(
+ sTemplateWifi, 5, 1024L, 1024L, SNOOZE_NEVER);
final long actualCycle = computeLastCycleBoundary(currentTime, policy);
assertEquals(expectedCycle, actualCycle);
}
@@ -392,7 +413,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z");
- final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 20, 1024L, 1024L);
+ final NetworkPolicy policy = new NetworkPolicy(
+ sTemplateWifi, 20, 1024L, 1024L, SNOOZE_NEVER);
final long actualCycle = computeLastCycleBoundary(currentTime, policy);
assertEquals(expectedCycle, actualCycle);
}
@@ -402,7 +424,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
final long currentTime = parseTime("2007-02-14T00:00:00.000Z");
final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z");
- final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 30, 1024L, 1024L);
+ final NetworkPolicy policy = new NetworkPolicy(
+ sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER);
final long actualCycle = computeLastCycleBoundary(currentTime, policy);
assertEquals(expectedCycle, actualCycle);
}
@@ -412,7 +435,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
final long currentTime = parseTime("2007-03-14T00:00:00.000Z");
final long expectedCycle = parseTime("2007-03-01T00:00:00.000Z");
- final NetworkPolicy policy = new NetworkPolicy(sTemplateWifi, 30, 1024L, 1024L);
+ final NetworkPolicy policy = new NetworkPolicy(
+ sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER);
final long actualCycle = computeLastCycleBoundary(currentTime, policy);
assertEquals(expectedCycle, actualCycle);
}
@@ -432,6 +456,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
state = new NetworkState[] { buildWifi() };
expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
expectTime(TIME_MAR_10 + elapsedRealtime);
+ expectClearNotifications();
future = expectMeteredIfacesChanged();
replay();
@@ -453,12 +478,14 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
// TODO: consider making strongly ordered mock
expectRemoveInterfaceQuota(TEST_IFACE);
expectSetInterfaceQuota(TEST_IFACE, 1536L);
+ expectRemoveInterfaceAlert(TEST_IFACE);
+ expectSetInterfaceAlert(TEST_IFACE, 512L);
expectClearNotifications();
future = expectMeteredIfacesChanged(TEST_IFACE);
replay();
- setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L));
+ setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
future.get();
verifyAndReset();
}
@@ -485,6 +512,131 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
verifyAndReset();
}
+ public void testOverWarningLimitNotification() throws Exception {
+ long elapsedRealtime = 0;
+ long currentTime = 0;
+ NetworkState[] state = null;
+ NetworkStats stats = null;
+ Future<Void> future;
+ Capture<String> tag;
+
+ final long TIME_FEB_15 = 1171497600000L;
+ final long TIME_MAR_10 = 1173484800000L;
+ final int CYCLE_DAY = 15;
+
+ // assign wifi policy
+ elapsedRealtime = 0;
+ currentTime = TIME_MAR_10 + elapsedRealtime;
+ state = new NetworkState[] {};
+
+ {
+ expectTime(currentTime);
+ expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+
+ expectClearNotifications();
+ future = expectMeteredIfacesChanged();
+
+ replay();
+ setNetworkPolicies(
+ new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
+ future.get();
+ verifyAndReset();
+ }
+
+ // bring up wifi network
+ elapsedRealtime += MINUTE_IN_MILLIS;
+ currentTime = TIME_MAR_10 + elapsedRealtime;
+ stats = new NetworkStats(elapsedRealtime, 1)
+ .addValues(TEST_IFACE, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L);
+ state = new NetworkState[] { buildWifi() };
+
+ {
+ expectTime(currentTime);
+ expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+ expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+ .andReturn(stats).atLeastOnce();
+
+ expectRemoveInterfaceQuota(TEST_IFACE);
+ expectSetInterfaceQuota(TEST_IFACE, 2048L);
+ expectRemoveInterfaceAlert(TEST_IFACE);
+ expectSetInterfaceAlert(TEST_IFACE, 1024L);
+
+ expectClearNotifications();
+ future = expectMeteredIfacesChanged(TEST_IFACE);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+ future.get();
+ verifyAndReset();
+ }
+
+ // go over warning, which should kick notification
+ elapsedRealtime += MINUTE_IN_MILLIS;
+ currentTime = TIME_MAR_10 + elapsedRealtime;
+ stats = new NetworkStats(elapsedRealtime, 1)
+ .addValues(TEST_IFACE, UID_ALL, TAG_NONE, 1536L, 15L, 0L, 0L);
+
+ {
+ expectTime(currentTime);
+ expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+ .andReturn(stats).atLeastOnce();
+
+ expectForceUpdate();
+ expectClearNotifications();
+ tag = expectEnqueueNotification();
+
+ replay();
+ mNetworkObserver.limitReached(null, TEST_IFACE);
+ assertNotificationType(TYPE_WARNING, tag.getValue());
+ verifyAndReset();
+ }
+
+ // go over limit, which should kick notification and dialog
+ elapsedRealtime += MINUTE_IN_MILLIS;
+ currentTime = TIME_MAR_10 + elapsedRealtime;
+ stats = new NetworkStats(elapsedRealtime, 1)
+ .addValues(TEST_IFACE, UID_ALL, TAG_NONE, 5120L, 512L, 0L, 0L);
+
+ {
+ expectTime(currentTime);
+ expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+ .andReturn(stats).atLeastOnce();
+
+ expectForceUpdate();
+ expectClearNotifications();
+ tag = expectEnqueueNotification();
+
+ replay();
+ mNetworkObserver.limitReached(null, TEST_IFACE);
+ assertNotificationType(TYPE_LIMIT, tag.getValue());
+ verifyAndReset();
+ }
+
+ // now snooze policy, which should remove quota
+ elapsedRealtime += MINUTE_IN_MILLIS;
+ currentTime = TIME_MAR_10 + elapsedRealtime;
+
+ {
+ expectTime(currentTime);
+ expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+ expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+ .andReturn(stats).atLeastOnce();
+
+ expectRemoveInterfaceQuota(TEST_IFACE);
+ expectRemoveInterfaceAlert(TEST_IFACE);
+
+ expectClearNotifications();
+ tag = expectEnqueueNotification();
+ future = expectMeteredIfacesChanged();
+
+ replay();
+ mService.snoozePolicy(sTemplateWifi);
+ future.get();
+ assertNotificationType(TYPE_LIMIT_SNOOZED, tag.getValue());
+ verifyAndReset();
+ }
+ }
+
private static long parseTime(String time) {
final Time result = new Time();
result.parse3339(time);
@@ -511,24 +663,46 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
}
+ private void expectForceUpdate() throws Exception {
+ mStatsService.forceUpdate();
+ expectLastCall().atLeastOnce();
+ }
+
private void expectClearNotifications() throws Exception {
mNotifManager.cancelNotificationWithTag(isA(String.class), isA(String.class), anyInt());
expectLastCall().anyTimes();
}
- private void expectSetInterfaceQuota(String iface, long quota) throws Exception {
- mNetworkManagement.setInterfaceQuota(iface, quota);
+ private Capture<String> expectEnqueueNotification() throws Exception {
+ final Capture<String> tag = new Capture<String>();
+ mNotifManager.enqueueNotificationWithTag(isA(String.class), capture(tag), anyInt(),
+ isA(Notification.class), isA(int[].class));
+ return tag;
+ }
+
+ private void expectSetInterfaceQuota(String iface, long quotaBytes) throws Exception {
+ mNetworkManager.setInterfaceQuota(iface, quotaBytes);
expectLastCall().atLeastOnce();
}
private void expectRemoveInterfaceQuota(String iface) throws Exception {
- mNetworkManagement.removeInterfaceQuota(iface);
+ mNetworkManager.removeInterfaceQuota(iface);
+ expectLastCall().atLeastOnce();
+ }
+
+ private void expectSetInterfaceAlert(String iface, long alertBytes) throws Exception {
+ mNetworkManager.setInterfaceAlert(iface, alertBytes);
+ expectLastCall().atLeastOnce();
+ }
+
+ private void expectRemoveInterfaceAlert(String iface) throws Exception {
+ mNetworkManager.removeInterfaceAlert(iface);
expectLastCall().atLeastOnce();
}
private void expectSetUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces)
throws Exception {
- mNetworkManagement.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
+ mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
expectLastCall().atLeastOnce();
}
@@ -563,15 +737,20 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
}
}
+ private static void assertNotificationType(int expected, String actualTag) {
+ assertEquals(
+ Integer.toString(expected), actualTag.substring(actualTag.lastIndexOf(':') + 1));
+ }
+
private void replay() {
EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener,
- mNetworkManagement, mTime, mConnManager, mNotifManager);
+ mNetworkManager, mTime, mConnManager, mNotifManager);
}
private void verifyAndReset() {
EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener,
- mNetworkManagement, mTime, mConnManager, mNotifManager);
+ mNetworkManager, mTime, mConnManager, mNotifManager);
EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener,
- mNetworkManagement, mTime, mConnManager, mNotifManager);
+ mNetworkManager, mTime, mConnManager, mNotifManager);
}
}