diff options
13 files changed, 771 insertions, 112 deletions
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 82495e381214..6fde746a556d 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 52cab30f9ccc..aaad8a1c7a98 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 593b2b74e301..1e9d81319297 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 03a6c07e6448..3704248e8076 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 e1a31f4d3e46..d69834112ced 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 d32df6edd111..ba9b5b0ae8d5 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 1b60b160e147..5ca77fc3da81 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 000000000000..723e338998f6 --- /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 a16f7481a173..39d2b1c30889 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 000000000000..0d1c3b2fed81 --- /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 435c394e5342..2e1e69bdead1 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 ee5f3f52b5d7..1620405ce43e 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 504ba42332c8..aab09ca93540 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); } } |