Add service to monitor/control the flow of data.

bug:2576057
Change-Id: Ib343c7ee1d619c6978910d9ee597db195d5aa3b6
diff --git a/Android.mk b/Android.mk
index cecc26a..10b6d67 100644
--- a/Android.mk
+++ b/Android.mk
@@ -117,6 +117,7 @@
 	core/java/android/hardware/ISensorService.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
+        core/java/android/net/IThrottleManager.aidl \
 	core/java/android/os/IMessenger.aidl \
 	core/java/android/os/storage/IMountService.aidl \
 	core/java/android/os/storage/IMountServiceListener.aidl \
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3a2aa55..30822d4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1398,7 +1398,7 @@
      * @see android.os.Vibrator
      */
     public static final String VIBRATOR_SERVICE = "vibrator";
-    
+
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.app.StatusBarManager} for interacting with the status bar.
@@ -1421,6 +1421,17 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.ThrottleManager} for handling management of
+     * throttling.
+     *
+     * @hide
+     * @see #getSystemService
+     * @see android.net.ThrottleManager
+     */
+    public static final String THROTTLE_SERVICE = "throttle";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.NetworkManagementService} for handling management of
      * system network services
      *
diff --git a/core/java/android/net/IThrottleManager.aidl b/core/java/android/net/IThrottleManager.aidl
new file mode 100644
index 0000000..298de6e
--- /dev/null
+++ b/core/java/android/net/IThrottleManager.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010, 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 android.net;
+
+import android.os.IBinder;
+
+/**
+ * Interface that answers queries about data transfer amounts and throttling
+ */
+/** {@hide} */
+interface IThrottleManager
+{
+    long getByteCount(String iface, int dir, int period, int ago);
+
+    int getThrottle(String iface);
+
+    long getResetTime(String iface);
+
+    long getPeriodStartTime(String iface);
+
+    long getCliffThreshold(String iface, int cliff);
+
+    int getCliffLevel(String iface, int cliff);
+}
diff --git a/core/java/android/net/ThrottleManager.java b/core/java/android/net/ThrottleManager.java
new file mode 100644
index 0000000..0500f6f
--- /dev/null
+++ b/core/java/android/net/ThrottleManager.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2008 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 android.net;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.os.Binder;
+import android.os.RemoteException;
+
+/**
+ * Class that handles throttling.  It provides read/write numbers per interface
+ * and methods to apply throttled rates.
+ * {@hide}
+ */
+public class ThrottleManager
+{
+    /**
+     * Broadcast each polling period to indicate new data counts.
+     *
+     * Includes four extras:
+     * EXTRA_CYCLE_READ - a long of the read bytecount for the current cycle
+     * EXTRA_CYCLE_WRITE -a long of the write bytecount for the current cycle
+     * EXTRA_CYLCE_START -a long of MS for the cycle start time
+     * EXTRA_CYCLE_END   -a long of MS for the cycle stop time
+     * {@hide}
+     */
+    public static final String THROTTLE_POLL_ACTION = "android.net.thrott.POLL_ACTION";
+    /**
+     * The lookup key for a long for the read bytecount for this period.  Retrieve with
+     * {@link android.content.Intent#getLongExtra(String)}.
+     * {@hide}
+     */
+    public static final String EXTRA_CYCLE_READ = "cycleRead";
+    /**
+     * contains a long of the number of bytes written in the cycle
+     * {@hide}
+     */
+    public static final String EXTRA_CYCLE_WRITE = "cycleWrite";
+    /**
+     * contains a long of the number of bytes read in the cycle
+     * {@hide}
+     */
+    public static final String EXTRA_CYCLE_START = "cycleStart";
+    /**
+     * contains a long of the ms since 1970 used to init a calendar, etc for the end
+     * of the cycle
+     * {@hide}
+     */
+    public static final String EXTRA_CYCLE_END = "cycleEnd";
+
+    /**
+     * Broadcast when the thottle level changes.
+     * {@hide}
+     */
+    public static final String THROTTLE_ACTION = "android.net.thrott.THROTTLE_ACTION";
+    /**
+     * int of the current bandwidth in TODO
+     * {@hide}
+     */
+    public static final String EXTRA_THROTTLE_LEVEL = "level";
+
+    // {@hide}
+    public static final int DIRECTION_TX = 0;
+    // {@hide}
+    public static final int DIRECTION_RX = 1;
+
+    // {@hide}
+    public static final int PERIOD_CYCLE  = 0;
+    // {@hide}
+    public static final int PERIOD_YEAR   = 1;
+    // {@hide}
+    public static final int PERIOD_MONTH  = 2;
+    // {@hide}
+    public static final int PERIOD_WEEK   = 3;
+    // @hide
+    public static final int PERIOD_7DAY   = 4;
+    // @hide
+    public static final int PERIOD_DAY    = 5;
+    // @hide
+    public static final int PERIOD_24HOUR = 6;
+    // @hide
+    public static final int PERIOD_HOUR   = 7;
+    // @hide
+    public static final int PERIOD_60MIN  = 8;
+    // @hide
+    public static final int PERIOD_MINUTE = 9;
+    // @hide
+    public static final int PERIOD_60SEC  = 10;
+    // @hide
+    public static final int PERIOD_SECOND = 11;
+
+    /**
+     * returns a long of the ms from the epoch to the time the current cycle ends for the
+     * named interface
+     * {@hide}
+     */
+    public long getResetTime(String iface) {
+        try {
+            return mService.getResetTime(iface);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * returns a long of the ms from the epoch to the time the current cycle started for the
+     * named interface
+     * {@hide}
+     */
+    public long getPeriodStartTime(String iface) {
+        try {
+            return mService.getPeriodStartTime(iface);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * returns a long of the byte count either read or written on the named interface
+     * for the period described.  Direction is either DIRECTION_RX or DIRECTION_TX and
+     * period may only be PERIOD_CYCLE for the current cycle (other periods may be supported
+     * in the future).  Ago indicates the number of periods in the past to lookup - 0 means
+     * the current period, 1 is the last one, 2 was two periods ago..
+     * {@hide}
+     */
+    public long getByteCount(String iface, int direction, int period, int ago) {
+        try {
+            return mService.getByteCount(iface, direction, period, ago);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * returns the number of bytes read+written after which a particular cliff
+     * takes effect on the named iface.  Currently only cliff #0 is supported (1 step)
+     * {@hide}
+     */
+    public long getCliffThreshold(String iface, int cliff) {
+        try {
+            return mService.getCliffThreshold(iface, cliff);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * returns the thottling bandwidth (bps) for a given cliff # on the named iface.
+     * only cliff #0 is currently supported.
+     * {@hide}
+     */
+    public int getCliffLevel(String iface, int cliff) {
+        try {
+            return mService.getCliffLevel(iface, cliff);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    private IThrottleManager mService;
+
+    /**
+     * Don't allow use of default constructor.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    private ThrottleManager() {
+    }
+
+    /**
+     * {@hide}
+     */
+    public ThrottleManager(IThrottleManager service) {
+        if (service == null) {
+            throw new IllegalArgumentException(
+                "ThrottleManager() cannot be constructed with null service");
+        }
+        mService = service;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c07ac31..eb14815 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3295,7 +3295,43 @@
          * @hide
          */
         public static final String DEFAULT_INSTALL_LOCATION = "default_install_location";
-        
+
+        /**
+         * The bandwidth throttle polling freqency in seconds
+         * @hide
+         */
+        public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec";
+
+        /**
+         * The bandwidth throttle threshold (long)
+         * @hide
+         */
+        public static final String THROTTLE_THRESHOLD = "throttle_threshold";
+
+        /**
+         * The bandwidth throttle value (kbps)
+         * @hide
+         */
+        public static final String THROTTLE_VALUE = "throttle_value";
+
+        /**
+         * The bandwidth throttle reset calendar day (1-28)
+         * @hide
+         */
+        public static final String THROTTLE_RESET_DAY = "throttle_reset_day";
+
+        /**
+         * The throttling notifications we should send
+         * @hide
+         */
+        public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type";
+
+        /**
+         * The interface we throttle
+         * @hide
+         */
+        public static final String THROTTLE_IFACE = "throttle_iface";
+
         /**
          * @hide
          */
diff --git a/core/res/res/drawable-hdpi/stat_sys_throttle_warning.png b/core/res/res/drawable-hdpi/stat_sys_throttle_warning.png
new file mode 100644
index 0000000..c42b00c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_throttle_warning.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_throttled.png b/core/res/res/drawable-hdpi/stat_sys_throttled.png
new file mode 100644
index 0000000..e43fbae
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_throttled.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png b/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png
new file mode 100644
index 0000000..3688803
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_throttled.png b/core/res/res/drawable-mdpi/stat_sys_throttled.png
new file mode 100644
index 0000000..efb64ad
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_throttled.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48d1ad7..36dc07c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2257,4 +2257,14 @@
     <!-- Shown when the device is tethered -->
     <string name="tethered_notification_title">Tethering active</string>
     <string name="tethered_notification_message">Touch to configure</string>
+
+    <!-- Strings for throttling notification -->
+    <!-- Shown when the user is in danger of being throttled -->
+    <string name="throttle_warning_notification_title">Excessive data use warning</string>
+    <string name="throttle_warning_notification_message">If your data use pattern continues you may be subject to bandwidth restrictions - touch for more information</string>
+
+    <!-- Strings for throttling notification -->
+    <!-- Shown when the users bandwidth is reduced because of excessive data use -->
+    <string name="throttled_notification_title">Bandwidth Restricted</string>
+    <string name="throttled_notification_message">Your mobile data bandwidth is being reduced because of excessive data use - touch for more information</string>
 </resources>
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 25a60a6..9d5d035 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -99,6 +99,7 @@
         DockObserver dock = null;
         UiModeManagerService uiMode = null;
         RecognitionManagerService recognition = null;
+        ThrottleService throttle = null;
 
         // Critical services...
         try {
@@ -269,6 +270,15 @@
             }
 
             try {
+                Slog.i(TAG, "Throttle Service");
+                throttle = new ThrottleService(context);
+                ServiceManager.addService(
+                        Context.THROTTLE_SERVICE, throttle);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting ThrottleService", e);
+            }
+
+            try {
               Slog.i(TAG, "Accessibility Manager");
               ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
                       new AccessibilityManagerService(context));
@@ -457,6 +467,7 @@
         final BatteryService batteryF = battery;
         final ConnectivityService connectivityF = connectivity;
         final DockObserver dockF = dock;
+        final ThrottleService throttleF = throttle;
         final UiModeManagerService uiModeF = uiMode;
         final AppWidgetService appWidgetF = appWidget;
         final WallpaperManagerService wallpaperF = wallpaper;
@@ -488,6 +499,7 @@
                 if (wallpaperF != null) wallpaperF.systemReady();
                 if (immF != null) immF.systemReady();
                 if (locationF != null) locationF.systemReady();
+                if (throttleF != null) throttleF.systemReady();
             }
         });
 
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
new file mode 100644
index 0000000..36931b2
--- /dev/null
+++ b/services/java/com/android/server/ThrottleService.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
+import android.net.IThrottleManager;
+import android.net.ThrottleManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Random;
+
+// TODO - add comments - reference the ThrottleManager for public API
+public class ThrottleService extends IThrottleManager.Stub {
+
+    private static final String TESTING_ENABLED_PROPERTY = "persist.throttle.testing";
+
+    private static final String TAG = "ThrottleService";
+    private static boolean DBG = true;
+    private Handler mHandler;
+    private HandlerThread mThread;
+
+    private Context mContext;
+
+    private int mPolicyPollPeriodSec;
+    private static final int DEFAULT_POLLING_PERIOD_SEC = 60 * 10;
+    private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1;
+
+    private static final int TESTING_RESET_PERIOD_SEC = 60 * 3;
+
+    private static final int PERIOD_COUNT = 6;
+
+    private long mPolicyThreshold;
+    // TODO - remove testing stuff?
+    private static final long DEFAULT_TESTING_THRESHOLD = 1 * 1024 * 1024;
+    private static final long DEFAULT_THRESHOLD = 0; // off by default
+
+    private int mPolicyThrottleValue;
+    private static final int DEFAULT_THROTTLE_VALUE = 100; // 100 Kbps
+
+    private int mPolicyResetDay; // 1-28
+
+    private long mLastRead; // read byte count from last poll
+    private long mLastWrite; // write byte count from last poll
+
+    private static final String ACTION_POLL = "com.android.server.ThrottleManager.action.POLL";
+    private static int POLL_REQUEST = 0;
+    private PendingIntent mPendingPollIntent;
+    private static final String ACTION_RESET = "com.android.server.ThorottleManager.action.RESET";
+    private static int RESET_REQUEST = 1;
+    private PendingIntent mPendingResetIntent;
+
+    private INetworkManagementService mNMService;
+    private AlarmManager mAlarmManager;
+    private NotificationManager mNotificationManager;
+
+    private DataRecorder mRecorder;
+
+    private int mThrottleLevel; // 0 for none, 1 for first throttle val, 2 for next, etc
+
+    private String mPolicyIface;
+
+    private static final int NOTIFICATION_WARNING   = 2;
+    private static final int NOTIFICATION_ALL       = 0xFFFFFFFF;
+    private int mPolicyNotificationsAllowedMask;
+
+    private Notification mThrottlingNotification;
+    private boolean mWarningNotificationSent = false;
+
+    public ThrottleService(Context context) {
+        if (DBG) Slog.d(TAG, "Starting ThrottleService");
+        mContext = context;
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent pollIntent = new Intent(ACTION_POLL, null);
+        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+        Intent resetIntent = new Intent(ACTION_RESET, null);
+        mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0);
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        mNMService = INetworkManagementService.Stub.asInterface(b);
+
+        mNotificationManager = (NotificationManager)mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE,
+                "ThrottleService");
+    }
+
+    public synchronized long getResetTime(String iface) {
+        enforceAccessPermission();
+        if (iface.equals(mPolicyIface) && (mRecorder != null)) mRecorder.getPeriodEnd();
+        return 0;
+    }
+    public synchronized long getPeriodStartTime(String iface) {
+        enforceAccessPermission();
+        if (iface.equals(mPolicyIface) && (mRecorder != null)) mRecorder.getPeriodStart();
+        return 0;
+    }
+    //TODO - a better name?  getCliffByteCountThreshold?
+    public synchronized long getCliffThreshold(String iface, int cliff) {
+        enforceAccessPermission();
+        if ((cliff == 0) && iface.equals(mPolicyIface)) {
+            return mPolicyThreshold;
+        }
+        return 0;
+    }
+    // TODO - a better name? getThrottleRate?
+    public synchronized int getCliffLevel(String iface, int cliff) {
+        enforceAccessPermission();
+        if ((cliff == 0) && iface.equals(mPolicyIface)) {
+            return mPolicyThrottleValue;
+        }
+        return 0;
+    }
+
+    public synchronized long getByteCount(String iface, int dir, int period, int ago) {
+        enforceAccessPermission();
+        if (iface.equals(mPolicyIface) &&
+                (period == ThrottleManager.PERIOD_CYCLE) &&
+                (mRecorder != null)) {
+            if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago);
+            if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago);
+        }
+        return 0;
+    }
+
+    // TODO - a better name - getCurrentThrottleRate?
+    public synchronized int getThrottle(String iface) {
+        enforceAccessPermission();
+        if (iface.equals(mPolicyIface) && (mThrottleLevel == 1)) {
+            return mPolicyThrottleValue;
+        }
+        return 0;
+    }
+
+    void systemReady() {
+        if (DBG) Slog.d(TAG, "systemReady");
+        mContext.registerReceiver(
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
+                }
+            }, new IntentFilter(ACTION_POLL));
+
+        mContext.registerReceiver(
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
+                }
+            }, new IntentFilter(ACTION_RESET));
+
+        // use a new thread as we don't want to stall the system for file writes
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new MyHandler(mThread.getLooper());
+        mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
+    }
+
+
+    private static final int EVENT_REBOOT_RECOVERY = 0;
+    private static final int EVENT_POLICY_CHANGED  = 1;
+    private static final int EVENT_POLL_ALARM      = 2;
+    private static final int EVENT_RESET_ALARM     = 3;
+    private class MyHandler extends Handler {
+        public MyHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case EVENT_REBOOT_RECOVERY:
+                onRebootRecovery();
+                break;
+            case EVENT_POLICY_CHANGED:
+                onPolicyChanged();
+                break;
+            case EVENT_POLL_ALARM:
+                onPollAlarm();
+                break;
+            case EVENT_RESET_ALARM:
+                onResetAlarm();
+            }
+        }
+
+        private void onRebootRecovery() {
+            if (DBG) Slog.d(TAG, "onRebootRecovery");
+            // check for sim change TODO
+            // reregister for notification of policy change
+
+            // register for roaming indication change
+            // check for roaming TODO
+
+            mRecorder = new DataRecorder(mContext, ThrottleService.this);
+
+            // get policy
+            mHandler.obtainMessage(EVENT_POLICY_CHANGED).sendToTarget();
+
+            // evaluate current conditions
+            mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
+        }
+
+        private void onSimChange() {
+            // TODO
+        }
+
+        // check for new policy info (threshold limit/value/etc)
+        private void onPolicyChanged() {
+            boolean testing = SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true");
+
+            int pollingPeriod = DEFAULT_POLLING_PERIOD_SEC;
+            if (testing) pollingPeriod = TESTING_POLLING_PERIOD_SEC;
+            mPolicyPollPeriodSec = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.THROTTLE_POLLING_SEC, pollingPeriod);
+
+            // TODO - remove testing stuff?
+            long defaultThreshold = DEFAULT_THRESHOLD;
+            if (testing) defaultThreshold = DEFAULT_TESTING_THRESHOLD;
+            synchronized (ThrottleService.this) {
+                mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(),
+                        Settings.Secure.THROTTLE_THRESHOLD, defaultThreshold);
+                mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(),
+                        Settings.Secure.THROTTLE_VALUE, DEFAULT_THROTTLE_VALUE);
+            }
+            mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.THROTTLE_RESET_DAY, -1);
+            if (mPolicyResetDay == -1 ||
+                    ((mPolicyResetDay < 1) || (mPolicyResetDay > 28))) {
+                Random g = new Random();
+                mPolicyResetDay = 1 + g.nextInt(28); // 1-28
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay);
+            }
+            synchronized (ThrottleService.this) {
+                mPolicyIface = Settings.Secure.getString(mContext.getContentResolver(),
+                        Settings.Secure.THROTTLE_IFACE);
+                // TODO - read default from resource so it's device-specific
+                if (mPolicyIface == null) mPolicyIface = "rmnet0";
+            }
+
+            mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.THROTTLE_NOTIFICATION_TYPE, NOTIFICATION_ALL);
+
+            Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + mPolicyPollPeriodSec +
+                    ", threshold=" + mPolicyThreshold + ", value=" + mPolicyThrottleValue +
+                    ", resetDay=" + mPolicyResetDay + ", noteType=" +
+                    mPolicyNotificationsAllowedMask);
+
+            Calendar end = calculatePeriodEnd();
+            Calendar start = calculatePeriodStart(end);
+
+            mRecorder.setNextPeriod(start,end);
+
+            mAlarmManager.cancel(mPendingResetIntent);
+            mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(),
+                    mPendingResetIntent);
+        }
+
+        private void onPollAlarm() {
+            long now = SystemClock.elapsedRealtime();
+            long next = now + mPolicyPollPeriodSec*1000;
+            long incRead = 0;
+            long incWrite = 0;
+            try {
+                incRead = mNMService.getInterfaceRxCounter(mPolicyIface) - mLastRead;
+                incWrite = mNMService.getInterfaceTxCounter(mPolicyIface) - mLastWrite;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "got remoteException in onPollAlarm:" + e);
+            }
+
+            mRecorder.addData(incRead, incWrite);
+
+            long periodRx = mRecorder.getPeriodRx(0);
+            long periodTx = mRecorder.getPeriodTx(0);
+            long total = periodRx + periodTx;
+            if (DBG) {
+                Slog.d(TAG, "onPollAlarm - now =" + now + ", read =" + incRead +
+                        ", written =" + incWrite + ", new total =" + total);
+            }
+            mLastRead += incRead;
+            mLastWrite += incWrite;
+
+            checkThrottleAndPostNotification(total);
+
+            Intent broadcast = new Intent(ThrottleManager.THROTTLE_POLL_ACTION);
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_READ, periodRx);
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx);
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, mRecorder.getPeriodStart());
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, mRecorder.getPeriodEnd());
+            mContext.sendStickyBroadcast(broadcast);
+
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, next, mPendingPollIntent);
+        }
+
+        private void checkThrottleAndPostNotification(long currentTotal) {
+            // are we even doing this?
+            if (mPolicyThreshold == 0)
+                return;
+
+            // check if we need to throttle
+            if (currentTotal > mPolicyThreshold) {
+                if (mThrottleLevel != 1) {
+                    synchronized (ThrottleService.this) {
+                        mThrottleLevel = 1;
+                    }
+                    if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!");
+                    try {
+                        mNMService.setInterfaceThrottle(mPolicyIface,
+                                mPolicyThrottleValue, mPolicyThrottleValue);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "error setting Throttle: " + e);
+                    }
+
+                    mNotificationManager.cancel(com.android.internal.R.drawable.
+                            stat_sys_throttle_warning);
+
+                    postNotification(com.android.internal.R.string.throttled_notification_title,
+                            com.android.internal.R.string.throttled_notification_message,
+                            com.android.internal.R.drawable.stat_sys_throttled);
+
+                    Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
+                    broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue);
+                    mContext.sendStickyBroadcast(broadcast);
+
+                } // else already up!
+            } else {
+                if ((mPolicyNotificationsAllowedMask & NOTIFICATION_WARNING) != 0) {
+                    // check if we should warn about throttle
+                    if (currentTotal > (mPolicyThreshold/2) && !mWarningNotificationSent) {
+                        mWarningNotificationSent = true;
+                        mNotificationManager.cancel(com.android.internal.R.drawable.
+                                stat_sys_throttle_warning);
+                        postNotification(com.android.internal.R.string.
+                                throttle_warning_notification_title,
+                                com.android.internal.R.string.
+                                throttle_warning_notification_message,
+                                com.android.internal.R.drawable.stat_sys_throttle_warning);
+                    } else {
+                        mWarningNotificationSent =false;
+                    }
+                }
+            }
+        }
+
+        private void postNotification(int titleInt, int messageInt, int icon) {
+            Intent intent = new Intent();
+            // TODO - fix up intent
+            intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
+            intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+
+            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+            Resources r = Resources.getSystem();
+            CharSequence title = r.getText(titleInt);
+            CharSequence message = r.getText(messageInt);
+            if (mThrottlingNotification == null) {
+                mThrottlingNotification = new Notification();
+                mThrottlingNotification.when = 0;
+                // TODO -  fixup icon
+                mThrottlingNotification.icon = icon;
+                mThrottlingNotification.defaults &= ~Notification.DEFAULT_SOUND;
+//                mThrottlingNotification.flags = Notification.FLAG_ONGOING_EVENT;
+            }
+            mThrottlingNotification.tickerText = title;
+            mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi);
+
+            mNotificationManager.notify(mThrottlingNotification.icon, mThrottlingNotification);
+        }
+
+
+        private synchronized void clearThrottleAndNotification() {
+            if (mThrottleLevel == 1) {
+                synchronized (ThrottleService.this) {
+                    mThrottleLevel = 0;
+                }
+                try {
+                    mNMService.setInterfaceThrottle(mPolicyIface, -1, -1);
+                } catch (Exception e) {
+                    Slog.e(TAG, "error clearing Throttle: " + e);
+                }
+                Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
+                broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1);
+                mContext.sendStickyBroadcast(broadcast);
+            }
+            mNotificationManager.cancel(com.android.internal.R.drawable.stat_sys_throttle_warning);
+            mNotificationManager.cancel(com.android.internal.R.drawable.stat_sys_throttled);
+            mWarningNotificationSent = false;
+        }
+
+        private Calendar calculatePeriodEnd() {
+            Calendar end = GregorianCalendar.getInstance();
+            int day = end.get(Calendar.DAY_OF_MONTH);
+            end.set(Calendar.DAY_OF_MONTH, mPolicyResetDay);
+            end.set(Calendar.HOUR_OF_DAY, 0);
+            end.set(Calendar.MINUTE, 0);
+            if (day >= mPolicyResetDay) {
+                int month = end.get(Calendar.MONTH);
+                if (month == Calendar.DECEMBER) {
+                    end.set(Calendar.YEAR, end.get(Calendar.YEAR) + 1);
+                    month = Calendar.JANUARY - 1;
+                }
+                end.set(Calendar.MONTH, month + 1);
+            }
+
+            // TODO - remove!
+            if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
+                end = GregorianCalendar.getInstance();
+                end.add(Calendar.SECOND, TESTING_RESET_PERIOD_SEC);
+            }
+            return end;
+        }
+        private Calendar calculatePeriodStart(Calendar end) {
+            Calendar start = (Calendar)end.clone();
+            int month = end.get(Calendar.MONTH);
+            if (end.get(Calendar.MONTH) == Calendar.JANUARY) {
+                month = Calendar.DECEMBER + 1;
+                start.set(Calendar.YEAR, start.get(Calendar.YEAR) - 1);
+            }
+            start.set(Calendar.MONTH, month - 1);
+
+            // TODO - remove!!
+            if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
+                start = (Calendar)end.clone();
+                start.add(Calendar.SECOND, -TESTING_RESET_PERIOD_SEC);
+            }
+            return start;
+        }
+
+        private void onResetAlarm() {
+            if (DBG) {
+                Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) +
+                        " bytes read and " + mRecorder.getPeriodTx(0) + " written");
+            }
+
+            Calendar end = calculatePeriodEnd();
+            Calendar start = calculatePeriodStart(end);
+
+            clearThrottleAndNotification();
+
+            mRecorder.setNextPeriod(start,end);
+
+            mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(),
+                    mPendingResetIntent);
+        }
+    }
+
+    // records bytecount data for a given time and accumulates it into larger time windows
+    // for logging and other purposes
+    //
+    // since time can be changed (user or network action) we will have to track the time of the
+    // last recording and deal with it.
+    private static class DataRecorder {
+        long[] mPeriodRxData;
+        long[] mPeriodTxData;
+        int mCurrentPeriod;
+        int mPeriodCount;
+
+        Calendar mPeriodStart;
+        Calendar mPeriodEnd;
+
+        ThrottleService mParent;
+        Context mContext;
+        SharedPreferences mSharedPreferences;
+
+        DataRecorder(Context context, ThrottleService parent) {
+            mContext = context;
+            mParent = parent;
+
+            synchronized (mParent) {
+                mPeriodCount = 6;
+                mPeriodRxData = new long[mPeriodCount];
+                mPeriodTxData = new long[mPeriodCount];
+
+                mPeriodStart = Calendar.getInstance();
+                mPeriodEnd = Calendar.getInstance();
+
+                mSharedPreferences = mContext.getSharedPreferences("ThrottleData",
+                        android.content.Context.MODE_PRIVATE);
+
+                zeroData(0);
+                retrieve();
+            }
+        }
+
+        void setNextPeriod(Calendar start, Calendar end) {
+            if (DBG) {
+                Slog.d(TAG, "setting next period to " + start.getTimeInMillis() +
+                        " --until-- " + end.getTimeInMillis());
+            }
+            // if we roll back in time to a previous period, toss out the current data
+            // if we roll forward to the next period, advance to the next
+
+            if (end.before(mPeriodStart)) {
+                if (DBG) {
+                    Slog.d(TAG, " old start was " + mPeriodStart.getTimeInMillis() + ", wiping");
+                }
+                synchronized (mParent) {
+                    mPeriodRxData[mCurrentPeriod] = 0;
+                    mPeriodTxData[mCurrentPeriod] = 0;
+                }
+            } else if(start.after(mPeriodEnd)) {
+                if (DBG) {
+                    Slog.d(TAG, " old end was " + mPeriodEnd.getTimeInMillis() + ", following");
+                }
+                synchronized (mParent) {
+                    ++mCurrentPeriod;
+                    if (mCurrentPeriod >= mPeriodCount) mCurrentPeriod = 0;
+                    mPeriodRxData[mCurrentPeriod] = 0;
+                    mPeriodTxData[mCurrentPeriod] = 0;
+                }
+            } else {
+                if (DBG) Slog.d(TAG, " we fit - ammending to last period");
+            }
+            setPeriodStart(start);
+            setPeriodEnd(end);
+            record();
+        }
+
+        public long getPeriodEnd() {
+            synchronized (mParent) {
+                return mPeriodEnd.getTimeInMillis();
+            }
+        }
+
+        private void setPeriodEnd(Calendar end) {
+            synchronized (mParent) {
+                mPeriodEnd = end;
+            }
+        }
+
+        public long getPeriodStart() {
+            synchronized (mParent) {
+                return mPeriodStart.getTimeInMillis();
+            }
+        }
+
+        private void setPeriodStart(Calendar start) {
+            synchronized (mParent) {
+                mPeriodStart = start;
+            }
+        }
+
+        public int getPeriodCount() {
+            synchronized (mParent) {
+                return mPeriodCount;
+            }
+        }
+
+        private void zeroData(int field) {
+            synchronized (mParent) {
+                for(int period = 0; period<mPeriodCount; period++) {
+                    mPeriodRxData[period] = 0;
+                    mPeriodTxData[period] = 0;
+                }
+                mCurrentPeriod = 0;
+            }
+
+        }
+
+        // if time moves backward accumulate all read/write that's lost into the now
+        // otherwise time moved forward.
+        void addData(long bytesRead, long bytesWritten) {
+            synchronized (mParent) {
+                mPeriodRxData[mCurrentPeriod] += bytesRead;
+                mPeriodTxData[mCurrentPeriod] += bytesWritten;
+            }
+            record();
+        }
+
+        private void record() {
+            // serialize into a secure setting
+
+            // 1 int mPeriodCount
+            // 13*6 long[PERIOD_COUNT] mPeriodRxData
+            // 13*6 long[PERIOD_COUNT] mPeriodTxData
+            // 1  int mCurrentPeriod
+            // 13 long periodStartMS
+            // 13 long periodEndMS
+            // 199 chars max
+            StringBuilder builder = new StringBuilder();
+            builder.append(mPeriodCount);
+            builder.append(":");
+            for(int i=0; i < mPeriodCount; i++) {
+                builder.append(mPeriodRxData[i]);
+                builder.append(":");
+            }
+            for(int i=0; i < mPeriodCount; i++) {
+                builder.append(mPeriodTxData[i]);
+                builder.append(":");
+            }
+            builder.append(mCurrentPeriod);
+            builder.append(":");
+            builder.append(mPeriodStart.getTimeInMillis());
+            builder.append(":");
+            builder.append(mPeriodEnd.getTimeInMillis());
+            builder.append(":");
+
+            SharedPreferences.Editor editor = mSharedPreferences.edit();
+
+            editor.putString("Data", builder.toString());
+            editor.commit();
+        }
+
+        private void retrieve() {
+            String data = mSharedPreferences.getString("Data", "");
+//            String data = Settings.Secure.getString(mContext.getContentResolver(),
+//                    Settings.Secure.THROTTLE_VALUE);
+            if (data == null || data.length() == 0) return;
+
+            synchronized (mParent) {
+                String[] parsed = data.split(":");
+                int parsedUsed = 0;
+                if (parsed.length < 6) return;
+
+                mPeriodCount = Integer.parseInt(parsed[parsedUsed++]);
+                if (parsed.length != 4 + (2 * mPeriodCount)) return;
+
+                mPeriodRxData = new long[mPeriodCount];
+                for(int i=0; i < mPeriodCount; i++) {
+                    mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
+                }
+                mPeriodTxData = new long[mPeriodCount];
+                for(int i=0; i < mPeriodCount; i++) {
+                    mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
+                }
+                mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]);
+                mPeriodStart = new GregorianCalendar();
+                mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+                mPeriodEnd = new GregorianCalendar();
+                mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+            }
+        }
+
+        long getPeriodRx(int which) {
+            if (DBG) { // TODO - remove
+                Slog.d(TAG, "reading slot "+ which +" with current =" + mCurrentPeriod);
+                for(int x = 0; x<mPeriodCount; x++) {
+                    Slog.d(TAG, "  " + x + " = " + mPeriodRxData[x]);
+                }
+            }
+            synchronized (mParent) {
+                if (which > mPeriodCount) return 0;
+                which = mCurrentPeriod - which;
+                if (which < 0) which += mPeriodCount;
+                return mPeriodRxData[which];
+            }
+        }
+        long getPeriodTx(int which) {
+            synchronized (mParent) {
+                if (which > mPeriodCount) return 0;
+                which = mCurrentPeriod - which;
+                if (which < 0) which += mPeriodCount;
+                return mPeriodTxData[which];
+            }
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ThrottleService " +
+                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
+                    Binder.getCallingUid());
+            return;
+        }
+        pw.println();
+
+        pw.println("The threshold is " + mPolicyThreshold +
+                ", after which you experince throttling to " +
+                mPolicyThrottleValue + "kbps");
+        pw.println("Current period is " +
+                (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
+                "and ends in " + (mRecorder.getPeriodEnd() - System.currentTimeMillis()) / 1000 +
+                " seconds.");
+        pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
+        for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
+            pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" +
+                    mRecorder.getPeriodTx(i));
+        }
+    }
+}