diff options
| author | 2010-04-01 14:45:18 -0700 | |
|---|---|---|
| committer | 2010-04-08 12:34:45 -0700 | |
| commit | 9e696c29f06d45d2891e1d38fd8d9033a9e21bb9 (patch) | |
| tree | 0ef181295c3ea1e8a0aa38b06b2bfe4e43a2da3b | |
| parent | ecb0e637870553f9c12dc6b9178de0512563af9c (diff) | |
Add service to monitor/control the flow of data.
bug:2576057
Change-Id: Ib343c7ee1d619c6978910d9ee597db195d5aa3b6
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 13 | ||||
| -rw-r--r-- | core/java/android/net/IThrottleManager.aidl | 38 | ||||
| -rw-r--r-- | core/java/android/net/ThrottleManager.java | 193 | ||||
| -rw-r--r-- | core/java/android/provider/Settings.java | 38 | ||||
| -rw-r--r-- | core/res/res/drawable-hdpi/stat_sys_throttle_warning.png | bin | 0 -> 2639 bytes | |||
| -rw-r--r-- | core/res/res/drawable-hdpi/stat_sys_throttled.png | bin | 0 -> 2503 bytes | |||
| -rw-r--r-- | core/res/res/drawable-mdpi/stat_sys_throttle_warning.png | bin | 0 -> 1670 bytes | |||
| -rw-r--r-- | core/res/res/drawable-mdpi/stat_sys_throttled.png | bin | 0 -> 1519 bytes | |||
| -rw-r--r-- | core/res/res/values/strings.xml | 10 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 12 | ||||
| -rw-r--r-- | services/java/com/android/server/ThrottleService.java | 729 |
12 files changed, 1032 insertions, 2 deletions
diff --git a/Android.mk b/Android.mk index cecc26a447de..10b6d6756242 100644 --- a/Android.mk +++ b/Android.mk @@ -117,6 +117,7 @@ LOCAL_SRC_FILES += \ 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 3a2aa552a026..30822d4ade1e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1398,7 +1398,7 @@ public abstract class Context { * @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 @@ public abstract class Context { /** * 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 000000000000..298de6e50ac6 --- /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 000000000000..0500f6f7db9f --- /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 c07ac31babba..eb14815c9e6f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3295,7 +3295,43 @@ public final class Settings { * @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 Binary files differnew file mode 100644 index 000000000000..c42b00c495f6 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_throttle_warning.png diff --git a/core/res/res/drawable-hdpi/stat_sys_throttled.png b/core/res/res/drawable-hdpi/stat_sys_throttled.png Binary files differnew file mode 100644 index 000000000000..e43fbaee4caf --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_throttled.png diff --git a/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png b/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png Binary files differnew file mode 100644 index 000000000000..368880328e2c --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_throttle_warning.png diff --git a/core/res/res/drawable-mdpi/stat_sys_throttled.png b/core/res/res/drawable-mdpi/stat_sys_throttled.png Binary files differnew file mode 100644 index 000000000000..efb64ad7b849 --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_throttled.png diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 48d1ad740069..36dc07c4c37e 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 25a60a6796ca..9d5d035b083a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -99,6 +99,7 @@ class ServerThread extends Thread { DockObserver dock = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; + ThrottleService throttle = null; // Critical services... try { @@ -269,6 +270,15 @@ class ServerThread extends Thread { } 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 @@ class ServerThread extends Thread { 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 @@ class ServerThread extends Thread { 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 000000000000..36931b23588c --- /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)); + } + } +} |