summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2011-06-14 17:27:29 -0700
committerJeff Sharkey <jsharkey@android.com>2011-06-14 22:43:08 -0700
commit497e4437af386930dff3bd55296d128bd4520959 (patch)
tree5e645f890e7a5072075de10255e7b032dcf1e12e
parent484a6c5b2495b2e1381d3867715239f8436ba373 (diff)
downloadframeworks_base-497e4437af386930dff3bd55296d128bd4520959.zip
frameworks_base-497e4437af386930dff3bd55296d128bd4520959.tar.gz
frameworks_base-497e4437af386930dff3bd55296d128bd4520959.tar.bz2
Data usage warning and limit notifications.
Watch for network statistics to cross NetworkPolicy warning or limit, and show notifications to user as needed. Currently checks during any statistics update, but will eventually move to event registration through netd when kernel supports. Fixed accounting bug in getSummaryForNetwork(). Only apply UID policy to applications; applying to system processes could break critical services like RIL. Change-Id: Iac0f20e910e205f3cbc54ec96395ff268b1aa379
-rw-r--r--core/java/android/net/NetworkPolicyManager.java9
-rwxr-xr-xcore/res/res/values/strings.xml15
-rw-r--r--services/java/com/android/server/SystemServer.java1
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java221
-rw-r--r--services/java/com/android/server/net/NetworkStatsService.java10
5 files changed, 250 insertions, 6 deletions
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 13ece40..e9d65e6 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -168,6 +168,15 @@ public class NetworkPolicyManager {
time.normalize(true);
}
+ /**
+ * Check if given UID can have a {@link #setUidPolicy(int, int)} defined,
+ * usually to protect critical system services.
+ */
+ public static boolean isUidValidForPolicy(Context context, int uid) {
+ return (uid >= android.os.Process.FIRST_APPLICATION_UID
+ && uid <= android.os.Process.LAST_APPLICATION_UID);
+ }
+
/** {@hide} */
public static void dumpPolicy(PrintWriter fout, int policy) {
fout.write("[");
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 966dbe5..5e13282 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2932,4 +2932,19 @@
<!-- Button text for the edit menu in input method extract mode. [CHAR LIMIT=16] -->
<string name="extract_edit_menu_button">Edit...</string>
+
+ <!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_warning_title">Data usage warning</string>
+ <!-- Notification body when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
+ <string name="data_usage_warning_body">usage exceeds <xliff:g id="size" example="3.8GB">%s</xliff:g></string>
+
+ <!-- Notification title when 2G-3G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
+ <string name="data_usage_3g_limit_title">2G-3G data disabled</string>
+ <!-- Notification title when 4G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
+ <string name="data_usage_4g_limit_title">4G data disabled</string>
+ <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
+ <string name="data_usage_mobile_limit_title">Mobile data disabled</string>
+ <!-- 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>
+
</resources>
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a820139..2769004 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -329,6 +329,7 @@ class ServerThread extends Thread {
Slog.i(TAG, "Notification Manager");
notification = new NotificationManagerService(context, statusBar, lights);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
+ networkPolicy.bindNotificationManager(notification);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Notification Manager", e);
}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 99569a8..2164334 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -20,9 +20,11 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -30,19 +32,27 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.dumpPolicy;
import static android.net.NetworkPolicyManager.dumpRules;
+import static android.net.NetworkPolicyManager.isUidValidForPolicy;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
+import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
import static android.net.TrafficStats.isNetworkTemplateMobile;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.IActivityManager;
+import android.app.INotificationManager;
import android.app.IProcessObserver;
+import android.app.Notification;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyListener;
@@ -58,6 +68,7 @@ import android.os.IPowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
+import android.text.format.Formatter;
import android.text.format.Time;
import android.util.NtpTrustedTime;
import android.util.Slog;
@@ -67,6 +78,7 @@ import android.util.SparseIntArray;
import android.util.TrustedTime;
import android.util.Xml;
+import com.android.internal.R;
import com.android.internal.os.AtomicFile;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Objects;
@@ -110,6 +122,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
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;
+
private static final String TAG_POLICY_LIST = "policy-list";
private static final String TAG_NETWORK_POLICY = "network-policy";
private static final String TAG_UID_POLICY = "uid-policy";
@@ -123,6 +138,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String ATTR_UID = "uid";
private static final String ATTR_POLICY = "policy";
+ public static final String ACTION_DATA_USAGE_WARNING =
+ "android.intent.action.DATA_USAGE_WARNING";
+ public static final String ACTION_DATA_USAGE_LIMIT =
+ "android.intent.action.DATA_USAGE_LIMIT";
+
private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
private final Context mContext;
@@ -132,6 +152,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final TrustedTime mTime;
private IConnectivityManager mConnManager;
+ private INotificationManager mNotifManager;
private final Object mRulesLock = new Object();
@@ -192,10 +213,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
}
+ public void bindNotificationManager(INotificationManager notifManager) {
+ mNotifManager = checkNotNull(notifManager, "missing INotificationManager");
+ }
+
public void systemReady() {
synchronized (mRulesLock) {
// read policy from disk
readPolicyLocked();
+ updateNotificationsLocked();
}
updateScreenOn();
@@ -221,6 +247,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
ifaceFilter.addAction(CONNECTIVITY_ACTION);
mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
+ // listen for warning polling events; currently dispatched by
+ final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
+ mContext.registerReceiver(
+ mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -229,6 +260,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
+ // skip when UID couldn't have any policy
+ if (!isUidValidForPolicy(mContext, uid)) return;
+
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
// remember all pid states and summarize foreground at uid level.
@@ -249,6 +283,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
+ // skip when UID couldn't have any policy
+ if (!isUidValidForPolicy(mContext, uid)) return;
+
synchronized (mRulesLock) {
// clear records and recompute, when they exist
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
@@ -272,6 +309,158 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
};
/**
+ * Receiver that watches for {@link INetworkStatsService} updates, which we
+ * use to check against {@link NetworkPolicy#warningBytes}.
+ */
+ private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // on background handler thread, and verified
+ // READ_NETWORK_USAGE_HISTORY permission above.
+
+ synchronized (mRulesLock) {
+ updateNotificationsLocked();
+ }
+ }
+ };
+
+ /**
+ * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
+ * to show visible notifications as needed.
+ */
+ private void updateNotificationsLocked() {
+ if (LOGV) Slog.v(TAG, "updateNotificationsLocked()");
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ // TODO: when switching to kernel notifications, compute next future
+ // cycle boundary to recompute notifications.
+
+ // examine stats for each policy defined
+ for (NetworkPolicy policy : mNetworkPolicy) {
+ final long start = computeLastCycleBoundary(currentTime, policy);
+ final long end = currentTime;
+
+ final long total;
+ try {
+ final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
+ start, end, policy.networkTemplate, policy.subscriberId);
+ total = stats.rx[0] + stats.tx[0];
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
+ continue;
+ }
+
+ if (policy.limitBytes != LIMIT_DISABLED && total >= policy.limitBytes) {
+ cancelNotification(policy, TYPE_WARNING);
+ enqueueNotification(policy, TYPE_LIMIT);
+ } else {
+ cancelNotification(policy, TYPE_LIMIT);
+
+ if (policy.warningBytes != WARNING_DISABLED && total >= policy.warningBytes) {
+ enqueueNotification(policy, TYPE_WARNING);
+ } else {
+ cancelNotification(policy, TYPE_WARNING);
+ }
+ }
+ }
+ }
+
+ /**
+ * Build unique tag that identifies an active {@link NetworkPolicy}
+ * notification of a specific type, like {@link #TYPE_LIMIT}.
+ */
+ private String buildNotificationTag(NetworkPolicy policy, int type) {
+ // TODO: consider splicing subscriberId hash into mix
+ return TAG + ":" + policy.networkTemplate + ":" + type;
+ }
+
+ /**
+ * 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) {
+ final String tag = buildNotificationTag(policy, type);
+ final Notification.Builder builder = new Notification.Builder(mContext);
+ builder.setOnlyAlertOnce(true);
+ builder.setOngoing(true);
+
+ 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,
+ Formatter.formatFileSize(mContext, policy.warningBytes));
+
+ builder.setSmallIcon(R.drawable.ic_menu_info_details);
+ builder.setTicker(title);
+ builder.setContentTitle(title);
+ builder.setContentText(body);
+ builder.setContentIntent(PendingIntent.getActivity(mContext, 0,
+ new Intent(ACTION_DATA_USAGE_WARNING),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ break;
+ }
+ case TYPE_LIMIT: {
+ final String title;
+ final String body = res.getString(R.string.data_usage_limit_body);
+ switch (policy.networkTemplate) {
+ case TEMPLATE_MOBILE_3G_LOWER:
+ title = res.getString(R.string.data_usage_3g_limit_title);
+ break;
+ case TEMPLATE_MOBILE_4G:
+ title = res.getString(R.string.data_usage_4g_limit_title);
+ break;
+ default:
+ title = res.getString(R.string.data_usage_mobile_limit_title);
+ break;
+ }
+
+ builder.setSmallIcon(com.android.internal.R.drawable.ic_menu_block);
+ builder.setTicker(title);
+ builder.setContentTitle(title);
+ builder.setContentText(body);
+ builder.setContentIntent(PendingIntent.getActivity(mContext, 0,
+ new Intent(ACTION_DATA_USAGE_LIMIT),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ break;
+ }
+ }
+
+ // TODO: move to NotificationManager once we can mock it
+ try {
+ final String packageName = mContext.getPackageName();
+ final int[] idReceived = new int[1];
+ mNotifManager.enqueueNotificationWithTag(
+ packageName, tag, 0x0, builder.getNotification(), idReceived);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem during enqueueNotification: " + e);
+ }
+ }
+
+ /**
+ * Cancel any notification for combined {@link NetworkPolicy} and specific
+ * type, like {@link #TYPE_LIMIT}.
+ */
+ private void cancelNotification(NetworkPolicy policy, int type) {
+ final String tag = buildNotificationTag(policy, type);
+
+ // TODO: move to NotificationManager once we can mock it
+ try {
+ final String packageName = mContext.getPackageName();
+ mNotifManager.cancelNotificationWithTag(packageName, tag, 0x0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem during enqueueNotification: " + e);
+ }
+ }
+
+ /**
* Receiver that watches for {@link IConnectivityManager} to claim network
* interfaces. Used to apply {@link NetworkPolicy} to matching networks.
*/
@@ -372,8 +561,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
// remaining "quota" is based on usage in current cycle
final long quota = Math.max(0, policy.limitBytes - total);
-
- // TODO: push quota rule down through NMS
+ //kernelSetIfacesQuota(ifaces, quota);
}
}
}
@@ -447,7 +635,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int uid = readIntAttribute(in, ATTR_UID);
final int policy = readIntAttribute(in, ATTR_POLICY);
- mUidPolicy.put(uid, policy);
+ if (isUidValidForPolicy(mContext, uid)) {
+ setUidPolicyUnchecked(uid, policy, false);
+ } else {
+ Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
+ }
}
}
}
@@ -495,6 +687,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int uid = mUidPolicy.keyAt(i);
final int policy = mUidPolicy.valueAt(i);
+ // skip writing empty policies
+ if (policy == POLICY_NONE) continue;
+
out.startTag(null, TAG_UID_POLICY);
writeIntAttribute(out, ATTR_UID, uid);
writeIntAttribute(out, ATTR_POLICY, policy);
@@ -516,6 +711,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
public void setUidPolicy(int uid, int policy) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+ if (!isUidValidForPolicy(mContext, uid)) {
+ throw new IllegalArgumentException("cannot apply policy to UID " + uid);
+ }
+
+ setUidPolicyUnchecked(uid, policy, true);
+ }
+
+ private void setUidPolicyUnchecked(int uid, int policy, boolean persist) {
final int oldPolicy;
synchronized (mRulesLock) {
oldPolicy = getUidPolicy(uid);
@@ -523,7 +726,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// uid policy changed, recompute rules and persist policy.
updateRulesForUidLocked(uid);
- writePolicyLocked();
+ if (persist) {
+ writePolicyLocked();
+ }
}
}
@@ -572,6 +777,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
updateIfacesLocked();
+ updateNotificationsLocked();
writePolicyLocked();
}
}
@@ -640,6 +846,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public boolean isUidForeground(int uid) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
synchronized (mRulesLock) {
// only really in foreground when screen is also on
return mUidForeground.get(uid, false) && mScreenOn;
@@ -696,6 +904,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
private void updateRulesForUidLocked(int uid) {
+ if (!isUidValidForPolicy(mContext, uid)) return;
+
final int uidPolicy = getUidPolicy(uid);
final boolean uidForeground = isUidForeground(uid);
@@ -711,6 +921,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// record rule locally to dispatch to new listeners
mUidRules.put(uid, uidRules);
+ final boolean rejectPaid = (uidRules & RULE_REJECT_PAID) != 0;
+ //kernelSetUidRejectPaid(uid, rejectPaid);
+
// dispatch changed rule to existing listeners
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index de69849..f762123 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -106,6 +106,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
// @VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
"com.android.server.action.NETWORK_STATS_POLL";
+ public static final String ACTION_NETWORK_STATS_UPDATED =
+ "com.android.server.action.NETWORK_STATS_UPDATED";
private PendingIntent mPollIntent;
@@ -203,7 +205,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
// listen for periodic polling events
- // TODO: switch to stronger internal permission
final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
@@ -298,7 +299,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
final NetworkStats stats = new NetworkStats(end - start, 1);
- stats.addEntry(IFACE_ALL, UID_ALL, tx, tx);
+ stats.addEntry(IFACE_ALL, UID_ALL, rx, tx);
return stats;
}
}
@@ -446,6 +447,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
break;
}
}
+
+ // finally, dispatch updated event to any listeners
+ final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
+ updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcast(updatedIntent, READ_NETWORK_USAGE_HISTORY);
}
/**