1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server; 18 19import android.app.AlarmManager; 20import android.app.Notification; 21import android.app.NotificationManager; 22import android.app.PendingIntent; 23import android.content.BroadcastReceiver; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.PackageManager; 29import android.content.res.Resources; 30import android.database.ContentObserver; 31import android.net.INetworkManagementEventObserver; 32import android.net.IThrottleManager; 33import android.net.SntpClient; 34import android.net.ThrottleManager; 35import android.os.Binder; 36import android.os.Environment; 37import android.os.Handler; 38import android.os.HandlerThread; 39import android.os.IBinder; 40import android.os.INetworkManagementService; 41import android.os.Looper; 42import android.os.Message; 43import android.os.RemoteException; 44import android.os.ServiceManager; 45import android.os.SystemClock; 46import android.os.SystemProperties; 47import android.provider.Settings; 48import android.telephony.TelephonyManager; 49import android.text.TextUtils; 50import android.util.Slog; 51 52import com.android.internal.R; 53import com.android.internal.telephony.TelephonyProperties; 54 55import java.io.BufferedWriter; 56import java.io.File; 57import java.io.FileDescriptor; 58import java.io.FileInputStream; 59import java.io.FileWriter; 60import java.io.IOException; 61import java.io.PrintWriter; 62import java.util.Calendar; 63import java.util.GregorianCalendar; 64import java.util.Properties; 65import java.util.Random; 66 67// TODO - add comments - reference the ThrottleManager for public API 68public class ThrottleService extends IThrottleManager.Stub { 69 70 private static final String TESTING_ENABLED_PROPERTY = "persist.throttle.testing"; 71 72 private static final String TAG = "ThrottleService"; 73 private static final boolean DBG = true; 74 private static final boolean VDBG = false; 75 private Handler mHandler; 76 private HandlerThread mThread; 77 78 private Context mContext; 79 80 private static final int INITIAL_POLL_DELAY_SEC = 90; 81 private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1; 82 private static final int TESTING_RESET_PERIOD_SEC = 60 * 10; 83 private static final long TESTING_THRESHOLD = 1 * 1024 * 1024; 84 85 private int mPolicyPollPeriodSec; 86 private long mPolicyThreshold; 87 private int mPolicyThrottleValue; 88 private int mPolicyResetDay; // 1-28 89 private int mPolicyNotificationsAllowedMask; 90 91 private long mLastRead; // read byte count from last poll 92 private long mLastWrite; // write byte count from last poll 93 94 private static final String ACTION_POLL = "com.android.server.ThrottleManager.action.POLL"; 95 private static int POLL_REQUEST = 0; 96 private PendingIntent mPendingPollIntent; 97 private static final String ACTION_RESET = "com.android.server.ThorottleManager.action.RESET"; 98 private static int RESET_REQUEST = 1; 99 private PendingIntent mPendingResetIntent; 100 101 private INetworkManagementService mNMService; 102 private AlarmManager mAlarmManager; 103 private NotificationManager mNotificationManager; 104 105 private DataRecorder mRecorder; 106 107 private String mIface; 108 109 private static final int NOTIFICATION_WARNING = 2; 110 111 private Notification mThrottlingNotification; 112 private boolean mWarningNotificationSent = false; 113 114 private InterfaceObserver mInterfaceObserver; 115 private SettingsObserver mSettingsObserver; 116 117 private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc 118 private static final int THROTTLE_INDEX_UNINITIALIZED = -1; 119 private static final int THROTTLE_INDEX_UNTHROTTLED = 0; 120 121 private static final String PROPERTIES_FILE = "/etc/gps.conf"; 122 private String mNtpServer; 123 private boolean mNtpActive; 124 125 public ThrottleService(Context context) { 126 if (VDBG) Slog.v(TAG, "Starting ThrottleService"); 127 mContext = context; 128 129 mNtpActive = false; 130 131 mIface = mContext.getResources().getString(R.string.config_datause_iface); 132 mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 133 Intent pollIntent = new Intent(ACTION_POLL, null); 134 mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); 135 Intent resetIntent = new Intent(ACTION_RESET, null); 136 mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0); 137 138 IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); 139 mNMService = INetworkManagementService.Stub.asInterface(b); 140 141 mNotificationManager = (NotificationManager)mContext.getSystemService( 142 Context.NOTIFICATION_SERVICE); 143 } 144 145 private static class InterfaceObserver extends INetworkManagementEventObserver.Stub { 146 private int mMsg; 147 private Handler mHandler; 148 private String mIface; 149 150 InterfaceObserver(Handler handler, int msg, String iface) { 151 super(); 152 mHandler = handler; 153 mMsg = msg; 154 mIface = iface; 155 } 156 157 public void interfaceLinkStatusChanged(String iface, boolean link) { 158 if (link) { 159 if (TextUtils.equals(iface, mIface)) { 160 mHandler.obtainMessage(mMsg).sendToTarget(); 161 } 162 } 163 } 164 165 public void interfaceAdded(String iface) { 166 // TODO - an interface added in the UP state should also trigger a StatusChanged 167 // notification.. 168 if (TextUtils.equals(iface, mIface)) { 169 mHandler.obtainMessage(mMsg).sendToTarget(); 170 } 171 } 172 173 public void interfaceRemoved(String iface) {} 174 } 175 176 177 private static class SettingsObserver extends ContentObserver { 178 private int mMsg; 179 private Handler mHandler; 180 SettingsObserver(Handler handler, int msg) { 181 super(handler); 182 mHandler = handler; 183 mMsg = msg; 184 } 185 186 void observe(Context context) { 187 ContentResolver resolver = context.getContentResolver(); 188 resolver.registerContentObserver(Settings.Secure.getUriFor( 189 Settings.Secure.THROTTLE_POLLING_SEC), false, this); 190 resolver.registerContentObserver(Settings.Secure.getUriFor( 191 Settings.Secure.THROTTLE_THRESHOLD_BYTES), false, this); 192 resolver.registerContentObserver(Settings.Secure.getUriFor( 193 Settings.Secure.THROTTLE_VALUE_KBITSPS), false, this); 194 resolver.registerContentObserver(Settings.Secure.getUriFor( 195 Settings.Secure.THROTTLE_RESET_DAY), false, this); 196 resolver.registerContentObserver(Settings.Secure.getUriFor( 197 Settings.Secure.THROTTLE_NOTIFICATION_TYPE), false, this); 198 resolver.registerContentObserver(Settings.Secure.getUriFor( 199 Settings.Secure.THROTTLE_HELP_URI), false, this); 200 resolver.registerContentObserver(Settings.Secure.getUriFor( 201 Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this); 202 } 203 204 @Override 205 public void onChange(boolean selfChange) { 206 mHandler.obtainMessage(mMsg).sendToTarget(); 207 } 208 } 209 210 private void enforceAccessPermission() { 211 mContext.enforceCallingOrSelfPermission( 212 android.Manifest.permission.ACCESS_NETWORK_STATE, 213 "ThrottleService"); 214 } 215 216 private long ntpToWallTime(long ntpTime) { 217 long bestNow = getBestTime(); 218 long localNow = System.currentTimeMillis(); 219 return localNow + (ntpTime - bestNow); 220 } 221 222 // TODO - fetch for the iface 223 // return time in the local, system wall time, correcting for the use of ntp 224 225 public synchronized long getResetTime(String iface) { 226 enforceAccessPermission(); 227 long resetTime = 0; 228 if (mRecorder != null) { 229 resetTime = ntpToWallTime(mRecorder.getPeriodEnd()); 230 } 231 return resetTime; 232 } 233 234 // TODO - fetch for the iface 235 // return time in the local, system wall time, correcting for the use of ntp 236 public synchronized long getPeriodStartTime(String iface) { 237 enforceAccessPermission(); 238 long startTime = 0; 239 if (mRecorder != null) { 240 startTime = ntpToWallTime(mRecorder.getPeriodStart()); 241 } 242 return startTime; 243 } 244 //TODO - a better name? getCliffByteCountThreshold? 245 // TODO - fetch for the iface 246 public synchronized long getCliffThreshold(String iface, int cliff) { 247 enforceAccessPermission(); 248 if (cliff == 1) { 249 return mPolicyThreshold; 250 } 251 return 0; 252 } 253 // TODO - a better name? getThrottleRate? 254 // TODO - fetch for the iface 255 public synchronized int getCliffLevel(String iface, int cliff) { 256 enforceAccessPermission(); 257 if (cliff == 1) { 258 return mPolicyThrottleValue; 259 } 260 return 0; 261 } 262 263 public String getHelpUri() { 264 enforceAccessPermission(); 265 return Settings.Secure.getString(mContext.getContentResolver(), 266 Settings.Secure.THROTTLE_HELP_URI); 267 } 268 269 // TODO - fetch for the iface 270 public synchronized long getByteCount(String iface, int dir, int period, int ago) { 271 enforceAccessPermission(); 272 if ((period == ThrottleManager.PERIOD_CYCLE) && 273 (mRecorder != null)) { 274 if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago); 275 if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago); 276 } 277 return 0; 278 } 279 280 // TODO - a better name - getCurrentThrottleRate? 281 // TODO - fetch for the iface 282 public synchronized int getThrottle(String iface) { 283 enforceAccessPermission(); 284 if (mThrottleIndex == 1) { 285 return mPolicyThrottleValue; 286 } 287 return 0; 288 } 289 290 void systemReady() { 291 if (VDBG) Slog.v(TAG, "systemReady"); 292 mContext.registerReceiver( 293 new BroadcastReceiver() { 294 @Override 295 public void onReceive(Context context, Intent intent) { 296 mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget(); 297 } 298 }, new IntentFilter(ACTION_POLL)); 299 300 mContext.registerReceiver( 301 new BroadcastReceiver() { 302 @Override 303 public void onReceive(Context context, Intent intent) { 304 mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); 305 } 306 }, new IntentFilter(ACTION_RESET)); 307 308 // use a new thread as we don't want to stall the system for file writes 309 mThread = new HandlerThread(TAG); 310 mThread.start(); 311 mHandler = new MyHandler(mThread.getLooper()); 312 mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); 313 314 mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface); 315 try { 316 mNMService.registerObserver(mInterfaceObserver); 317 } catch (RemoteException e) { 318 Slog.e(TAG, "Could not register InterfaceObserver " + e); 319 } 320 321 mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); 322 mSettingsObserver.observe(mContext); 323 324 FileInputStream stream = null; 325 try { 326 Properties properties = new Properties(); 327 File file = new File(PROPERTIES_FILE); 328 stream = new FileInputStream(file); 329 properties.load(stream); 330 mNtpServer = properties.getProperty("NTP_SERVER", null); 331 } catch (IOException e) { 332 Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); 333 } finally { 334 if (stream != null) { 335 try { 336 stream.close(); 337 } catch (Exception e) {} 338 } 339 } 340 } 341 342 343 private static final int EVENT_REBOOT_RECOVERY = 0; 344 private static final int EVENT_POLICY_CHANGED = 1; 345 private static final int EVENT_POLL_ALARM = 2; 346 private static final int EVENT_RESET_ALARM = 3; 347 private static final int EVENT_IFACE_UP = 4; 348 private class MyHandler extends Handler { 349 public MyHandler(Looper l) { 350 super(l); 351 } 352 353 @Override 354 public void handleMessage(Message msg) { 355 switch (msg.what) { 356 case EVENT_REBOOT_RECOVERY: 357 onRebootRecovery(); 358 break; 359 case EVENT_POLICY_CHANGED: 360 onPolicyChanged(); 361 break; 362 case EVENT_POLL_ALARM: 363 onPollAlarm(); 364 break; 365 case EVENT_RESET_ALARM: 366 onResetAlarm(); 367 break; 368 case EVENT_IFACE_UP: 369 onIfaceUp(); 370 } 371 } 372 373 private void onRebootRecovery() { 374 if (VDBG) Slog.v(TAG, "onRebootRecovery"); 375 // check for sim change TODO 376 // reregister for notification of policy change 377 378 mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; 379 380 mRecorder = new DataRecorder(mContext, ThrottleService.this); 381 382 // get policy 383 mHandler.obtainMessage(EVENT_POLICY_CHANGED).sendToTarget(); 384 385 // if we poll now we won't have network connectivity or even imsi access 386 // queue up a poll to happen in a little while - after ntp and imsi are avail 387 // TODO - make this callback based (ie, listen for notificaitons) 388 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_POLL_ALARM), 389 INITIAL_POLL_DELAY_SEC * 1000); 390 } 391 392 // check for new policy info (threshold limit/value/etc) 393 private void onPolicyChanged() { 394 boolean testing = SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true"); 395 396 int pollingPeriod = mContext.getResources().getInteger( 397 R.integer.config_datause_polling_period_sec); 398 mPolicyPollPeriodSec = Settings.Secure.getInt(mContext.getContentResolver(), 399 Settings.Secure.THROTTLE_POLLING_SEC, pollingPeriod); 400 401 // TODO - remove testing stuff? 402 long defaultThreshold = mContext.getResources().getInteger( 403 R.integer.config_datause_threshold_bytes); 404 int defaultValue = mContext.getResources().getInteger( 405 R.integer.config_datause_throttle_kbitsps); 406 synchronized (ThrottleService.this) { 407 mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(), 408 Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold); 409 mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(), 410 Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue); 411 if (testing) { 412 mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC; 413 mPolicyThreshold = TESTING_THRESHOLD; 414 } 415 } 416 417 mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(), 418 Settings.Secure.THROTTLE_RESET_DAY, -1); 419 if (mPolicyResetDay == -1 || 420 ((mPolicyResetDay < 1) || (mPolicyResetDay > 28))) { 421 Random g = new Random(); 422 mPolicyResetDay = 1 + g.nextInt(28); // 1-28 423 Settings.Secure.putInt(mContext.getContentResolver(), 424 Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay); 425 } 426 synchronized (ThrottleService.this) { 427 if (mIface == null) { 428 mPolicyThreshold = 0; 429 } 430 } 431 432 int defaultNotificationType = mContext.getResources().getInteger( 433 R.integer.config_datause_notification_type); 434 mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(), 435 Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType); 436 437 mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), 438 Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC); 439 440 if (VDBG || (mPolicyThreshold != 0)) { 441 Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + 442 mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold + ", value=" + 443 mPolicyThrottleValue + ", resetDay=" + mPolicyResetDay + ", noteType=" + 444 mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + mMaxNtpCacheAgeSec); 445 } 446 447 // force updates 448 mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; 449 450 onResetAlarm(); 451 452 onPollAlarm(); 453 454 Intent broadcast = new Intent(ThrottleManager.POLICY_CHANGED_ACTION); 455 mContext.sendBroadcast(broadcast); 456 } 457 458 private void onPollAlarm() { 459 long now = SystemClock.elapsedRealtime(); 460 long next = now + mPolicyPollPeriodSec*1000; 461 462 checkForAuthoritativeTime(); 463 464 long incRead = 0; 465 long incWrite = 0; 466 try { 467 incRead = mNMService.getInterfaceRxCounter(mIface) - mLastRead; 468 incWrite = mNMService.getInterfaceTxCounter(mIface) - mLastWrite; 469 // handle iface resets - on some device the 3g iface comes and goes and gets 470 // totals reset to 0. Deal with it 471 if ((incRead < 0) || (incWrite < 0)) { 472 incRead += mLastRead; 473 incWrite += mLastWrite; 474 mLastRead = 0; 475 mLastWrite = 0; 476 } 477 } catch (RemoteException e) { 478 Slog.e(TAG, "got remoteException in onPollAlarm:" + e); 479 } 480 // don't count this data if we're roaming. 481 boolean roaming = "true".equals( 482 SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING)); 483 if (!roaming) { 484 mRecorder.addData(incRead, incWrite); 485 } 486 487 long periodRx = mRecorder.getPeriodRx(0); 488 long periodTx = mRecorder.getPeriodTx(0); 489 long total = periodRx + periodTx; 490 if (VDBG || (mPolicyThreshold != 0)) { 491 Slog.d(TAG, "onPollAlarm - roaming =" + roaming + 492 ", read =" + incRead + ", written =" + incWrite + ", new total =" + total); 493 } 494 mLastRead += incRead; 495 mLastWrite += incWrite; 496 497 checkThrottleAndPostNotification(total); 498 499 Intent broadcast = new Intent(ThrottleManager.THROTTLE_POLL_ACTION); 500 broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_READ, periodRx); 501 broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx); 502 broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface)); 503 broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface)); 504 mContext.sendStickyBroadcast(broadcast); 505 506 mAlarmManager.cancel(mPendingPollIntent); 507 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); 508 } 509 510 private void onIfaceUp() { 511 // if we were throttled before, be sure and set it again - the iface went down 512 // (and may have disappeared all together) and these settings were lost 513 if (mThrottleIndex == 1) { 514 try { 515 mNMService.setInterfaceThrottle(mIface, -1, -1); 516 mNMService.setInterfaceThrottle(mIface, 517 mPolicyThrottleValue, mPolicyThrottleValue); 518 } catch (Exception e) { 519 Slog.e(TAG, "error setting Throttle: " + e); 520 } 521 } 522 } 523 524 private void checkThrottleAndPostNotification(long currentTotal) { 525 // is throttling enabled? 526 if (mPolicyThreshold == 0) { 527 clearThrottleAndNotification(); 528 return; 529 } 530 531 // have we spoken with an ntp server yet? 532 // this is controversial, but we'd rather err towards not throttling 533 if ((mNtpServer != null) && !mNtpActive) { 534 return; 535 } 536 537 // check if we need to throttle 538 if (currentTotal > mPolicyThreshold) { 539 if (mThrottleIndex != 1) { 540 synchronized (ThrottleService.this) { 541 mThrottleIndex = 1; 542 } 543 if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!"); 544 try { 545 mNMService.setInterfaceThrottle(mIface, 546 mPolicyThrottleValue, mPolicyThrottleValue); 547 } catch (Exception e) { 548 Slog.e(TAG, "error setting Throttle: " + e); 549 } 550 551 mNotificationManager.cancel(R.drawable.stat_sys_throttled); 552 553 postNotification(R.string.throttled_notification_title, 554 R.string.throttled_notification_message, 555 R.drawable.stat_sys_throttled, 556 Notification.FLAG_ONGOING_EVENT); 557 558 Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); 559 broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue); 560 mContext.sendStickyBroadcast(broadcast); 561 562 } // else already up! 563 } else { 564 clearThrottleAndNotification(); 565 if ((mPolicyNotificationsAllowedMask & NOTIFICATION_WARNING) != 0) { 566 // check if we should warn about throttle 567 // pretend we only have 1/2 the time remaining that we actually do 568 // if our burn rate in the period so far would have us exceed the limit 569 // in that 1/2 window, warn the user. 570 // this gets more generous in the early to middle period and converges back 571 // to the limit as we move toward the period end. 572 573 // adding another factor - it must be greater than the total cap/4 574 // else we may get false alarms very early in the period.. in the first 575 // tenth of a percent of the period if we used more than a tenth of a percent 576 // of the cap we'd get a warning and that's not desired. 577 long start = mRecorder.getPeriodStart(); 578 long end = mRecorder.getPeriodEnd(); 579 long periodLength = end - start; 580 long now = System.currentTimeMillis(); 581 long timeUsed = now - start; 582 long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength); 583 if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) { 584 if (mWarningNotificationSent == false) { 585 mWarningNotificationSent = true; 586 mNotificationManager.cancel(R.drawable.stat_sys_throttled); 587 postNotification(R.string.throttle_warning_notification_title, 588 R.string.throttle_warning_notification_message, 589 R.drawable.stat_sys_throttled, 590 0); 591 } 592 } else { 593 if (mWarningNotificationSent == true) { 594 mNotificationManager.cancel(R.drawable.stat_sys_throttled); 595 mWarningNotificationSent =false; 596 } 597 } 598 } 599 } 600 } 601 602 private void postNotification(int titleInt, int messageInt, int icon, int flags) { 603 Intent intent = new Intent(); 604 // TODO - fix up intent 605 intent.setClassName("com.android.phone", "com.android.phone.DataUsage"); 606 intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 607 608 PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); 609 610 Resources r = Resources.getSystem(); 611 CharSequence title = r.getText(titleInt); 612 CharSequence message = r.getText(messageInt); 613 if (mThrottlingNotification == null) { 614 mThrottlingNotification = new Notification(); 615 mThrottlingNotification.when = 0; 616 // TODO - fixup icon 617 mThrottlingNotification.icon = icon; 618 mThrottlingNotification.defaults &= ~Notification.DEFAULT_SOUND; 619 } 620 mThrottlingNotification.flags = flags; 621 mThrottlingNotification.tickerText = title; 622 mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi); 623 624 mNotificationManager.notify(mThrottlingNotification.icon, mThrottlingNotification); 625 } 626 627 628 private synchronized void clearThrottleAndNotification() { 629 if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) { 630 synchronized (ThrottleService.this) { 631 mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED; 632 } 633 try { 634 mNMService.setInterfaceThrottle(mIface, -1, -1); 635 } catch (Exception e) { 636 Slog.e(TAG, "error clearing Throttle: " + e); 637 } 638 Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); 639 broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1); 640 mContext.sendStickyBroadcast(broadcast); 641 mNotificationManager.cancel(R.drawable.stat_sys_throttled); 642 mWarningNotificationSent = false; 643 } 644 } 645 646 private Calendar calculatePeriodEnd(long now) { 647 Calendar end = GregorianCalendar.getInstance(); 648 end.setTimeInMillis(now); 649 int day = end.get(Calendar.DAY_OF_MONTH); 650 end.set(Calendar.DAY_OF_MONTH, mPolicyResetDay); 651 end.set(Calendar.HOUR_OF_DAY, 0); 652 end.set(Calendar.MINUTE, 0); 653 end.set(Calendar.SECOND, 0); 654 end.set(Calendar.MILLISECOND, 0); 655 if (day >= mPolicyResetDay) { 656 int month = end.get(Calendar.MONTH); 657 if (month == Calendar.DECEMBER) { 658 end.set(Calendar.YEAR, end.get(Calendar.YEAR) + 1); 659 month = Calendar.JANUARY - 1; 660 } 661 end.set(Calendar.MONTH, month + 1); 662 } 663 664 // TODO - remove! 665 if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) { 666 end = GregorianCalendar.getInstance(); 667 end.setTimeInMillis(now); 668 end.add(Calendar.SECOND, TESTING_RESET_PERIOD_SEC); 669 } 670 return end; 671 } 672 private Calendar calculatePeriodStart(Calendar end) { 673 Calendar start = (Calendar)end.clone(); 674 int month = end.get(Calendar.MONTH); 675 if (end.get(Calendar.MONTH) == Calendar.JANUARY) { 676 month = Calendar.DECEMBER + 1; 677 start.set(Calendar.YEAR, start.get(Calendar.YEAR) - 1); 678 } 679 start.set(Calendar.MONTH, month - 1); 680 681 // TODO - remove!! 682 if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) { 683 start = (Calendar)end.clone(); 684 start.add(Calendar.SECOND, -TESTING_RESET_PERIOD_SEC); 685 } 686 return start; 687 } 688 689 private void onResetAlarm() { 690 if (VDBG || (mPolicyThreshold != 0)) { 691 Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) + 692 " bytes read and " + mRecorder.getPeriodTx(0) + " written"); 693 } 694 695 long now = getBestTime(); 696 697 if (mNtpActive || (mNtpServer == null)) { 698 Calendar end = calculatePeriodEnd(now); 699 Calendar start = calculatePeriodStart(end); 700 701 if (mRecorder.setNextPeriod(start, end)) { 702 onPollAlarm(); 703 } 704 705 mAlarmManager.cancel(mPendingResetIntent); 706 long offset = end.getTimeInMillis() - now; 707 // use Elapsed realtime so clock changes don't fool us. 708 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, 709 SystemClock.elapsedRealtime() + offset, 710 mPendingResetIntent); 711 } else { 712 if (VDBG) Slog.d(TAG, "no authoritative time - not resetting period"); 713 } 714 } 715 } 716 717 private void checkForAuthoritativeTime() { 718 if (mNtpActive || (mNtpServer == null)) return; 719 720 // will try to get the ntp time and switch to it if found. 721 // will also cache the time so we don't fetch it repeatedly. 722 getBestTime(); 723 } 724 725 private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day 726 private static final int MAX_NTP_FETCH_WAIT = 10 * 1000; 727 private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC; 728 private long cachedNtp; 729 private long cachedNtpTimestamp; 730 731 private long getBestTime() { 732 if (mNtpServer != null) { 733 if (mNtpActive) { 734 long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp; 735 if (ntpAge < mMaxNtpCacheAgeSec * 1000) { 736 if (VDBG) Slog.v(TAG, "using cached time"); 737 return cachedNtp + ntpAge; 738 } 739 } 740 SntpClient client = new SntpClient(); 741 if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT)) { 742 cachedNtp = client.getNtpTime(); 743 cachedNtpTimestamp = SystemClock.elapsedRealtime(); 744 if (!mNtpActive) { 745 mNtpActive = true; 746 if (VDBG) Slog.d(TAG, "found Authoritative time - reseting alarm"); 747 mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); 748 } 749 if (VDBG) Slog.v(TAG, "using Authoritative time: " + cachedNtp); 750 return cachedNtp; 751 } 752 } 753 long time = System.currentTimeMillis(); 754 if (VDBG) Slog.v(TAG, "using User time: " + time); 755 mNtpActive = false; 756 return time; 757 } 758 759 // records bytecount data for a given time and accumulates it into larger time windows 760 // for logging and other purposes 761 // 762 // since time can be changed (user or network action) we will have to track the time of the 763 // last recording and deal with it. 764 private static class DataRecorder { 765 long[] mPeriodRxData; 766 long[] mPeriodTxData; 767 int mCurrentPeriod; 768 int mPeriodCount; 769 770 Calendar mPeriodStart; 771 Calendar mPeriodEnd; 772 773 ThrottleService mParent; 774 Context mContext; 775 String mImsi = null; 776 777 TelephonyManager mTelephonyManager; 778 779 DataRecorder(Context context, ThrottleService parent) { 780 mContext = context; 781 mParent = parent; 782 783 mTelephonyManager = (TelephonyManager)mContext.getSystemService( 784 Context.TELEPHONY_SERVICE); 785 786 synchronized (mParent) { 787 mPeriodCount = 6; 788 mPeriodRxData = new long[mPeriodCount]; 789 mPeriodTxData = new long[mPeriodCount]; 790 791 mPeriodStart = Calendar.getInstance(); 792 mPeriodEnd = Calendar.getInstance(); 793 794 retrieve(); 795 } 796 } 797 798 boolean setNextPeriod(Calendar start, Calendar end) { 799 // TODO - how would we deal with a dual-IMSI device? 800 checkForSubscriberId(); 801 boolean startNewPeriod = true; 802 803 if (start.equals(mPeriodStart) && end.equals(mPeriodEnd)) { 804 // same endpoints - keep collecting 805 if (VDBG) { 806 Slog.d(TAG, "same period (" + start.getTimeInMillis() + "," + 807 end.getTimeInMillis() +") - ammending data"); 808 } 809 startNewPeriod = false; 810 } else { 811 if (VDBG) { 812 if(start.equals(mPeriodEnd) || start.after(mPeriodEnd)) { 813 Slog.d(TAG, "next period (" + start.getTimeInMillis() + "," + 814 end.getTimeInMillis() + ") - old end was " + 815 mPeriodEnd.getTimeInMillis() + ", following"); 816 } else { 817 Slog.d(TAG, "new period (" + start.getTimeInMillis() + "," + 818 end.getTimeInMillis() + ") replacing old (" + 819 mPeriodStart.getTimeInMillis() + "," + 820 mPeriodEnd.getTimeInMillis() + ")"); 821 } 822 } 823 synchronized (mParent) { 824 ++mCurrentPeriod; 825 if (mCurrentPeriod >= mPeriodCount) mCurrentPeriod = 0; 826 mPeriodRxData[mCurrentPeriod] = 0; 827 mPeriodTxData[mCurrentPeriod] = 0; 828 } 829 } 830 setPeriodStart(start); 831 setPeriodEnd(end); 832 record(); 833 return startNewPeriod; 834 } 835 836 public long getPeriodEnd() { 837 synchronized (mParent) { 838 return mPeriodEnd.getTimeInMillis(); 839 } 840 } 841 842 private void setPeriodEnd(Calendar end) { 843 synchronized (mParent) { 844 mPeriodEnd = end; 845 } 846 } 847 848 public long getPeriodStart() { 849 synchronized (mParent) { 850 return mPeriodStart.getTimeInMillis(); 851 } 852 } 853 854 private void setPeriodStart(Calendar start) { 855 synchronized (mParent) { 856 mPeriodStart = start; 857 } 858 } 859 860 public int getPeriodCount() { 861 synchronized (mParent) { 862 return mPeriodCount; 863 } 864 } 865 866 private void zeroData(int field) { 867 synchronized (mParent) { 868 for(int period = 0; period<mPeriodCount; period++) { 869 mPeriodRxData[period] = 0; 870 mPeriodTxData[period] = 0; 871 } 872 mCurrentPeriod = 0; 873 } 874 875 } 876 877 // if time moves backward accumulate all read/write that's lost into the now 878 // otherwise time moved forward. 879 void addData(long bytesRead, long bytesWritten) { 880 checkForSubscriberId(); 881 882 synchronized (mParent) { 883 mPeriodRxData[mCurrentPeriod] += bytesRead; 884 mPeriodTxData[mCurrentPeriod] += bytesWritten; 885 } 886 record(); 887 } 888 889 private File getDataFile() { 890 File dataDir = Environment.getDataDirectory(); 891 File throttleDir = new File(dataDir, "system/throttle"); 892 throttleDir.mkdirs(); 893 String mImsi = mTelephonyManager.getSubscriberId(); 894 File dataFile; 895 if (mImsi == null) { 896 dataFile = useMRUFile(throttleDir); 897 if (VDBG) Slog.v(TAG, "imsi not available yet, using " + dataFile); 898 } else { 899 String imsiHash = Integer.toString(mImsi.hashCode()); 900 dataFile = new File(throttleDir, imsiHash); 901 } 902 // touch the file so it's not LRU 903 dataFile.setLastModified(System.currentTimeMillis()); 904 checkAndDeleteLRUDataFile(throttleDir); 905 return dataFile; 906 } 907 908 // TODO - get broadcast (TelephonyIntents.ACTION_SIM_STATE_CHANGED) instead of polling 909 private void checkForSubscriberId() { 910 if (mImsi != null) return; 911 912 mImsi = mTelephonyManager.getSubscriberId(); 913 if (mImsi == null) return; 914 915 if (VDBG) Slog.d(TAG, "finally have imsi - retreiving data"); 916 retrieve(); 917 } 918 919 private final static int MAX_SIMS_SUPPORTED = 3; 920 921 private void checkAndDeleteLRUDataFile(File dir) { 922 File[] files = dir.listFiles(); 923 924 if (files.length <= MAX_SIMS_SUPPORTED) return; 925 if (DBG) Slog.d(TAG, "Too many data files"); 926 do { 927 File oldest = null; 928 for (File f : files) { 929 if ((oldest == null) || (oldest.lastModified() > f.lastModified())) { 930 oldest = f; 931 } 932 } 933 if (oldest == null) return; 934 if (DBG) Slog.d(TAG, " deleting " + oldest); 935 oldest.delete(); 936 files = dir.listFiles(); 937 } while (files.length > MAX_SIMS_SUPPORTED); 938 } 939 940 private File useMRUFile(File dir) { 941 File newest = null; 942 File[] files = dir.listFiles(); 943 944 for (File f : files) { 945 if ((newest == null) || (newest.lastModified() < f.lastModified())) { 946 newest = f; 947 } 948 } 949 if (newest == null) { 950 newest = new File(dir, "temp"); 951 } 952 return newest; 953 } 954 955 956 private static final int DATA_FILE_VERSION = 1; 957 958 private void record() { 959 // 1 int version 960 // 1 int mPeriodCount 961 // 13*6 long[PERIOD_COUNT] mPeriodRxData 962 // 13*6 long[PERIOD_COUNT] mPeriodTxData 963 // 1 int mCurrentPeriod 964 // 13 long periodStartMS 965 // 13 long periodEndMS 966 // 200 chars max 967 StringBuilder builder = new StringBuilder(); 968 builder.append(DATA_FILE_VERSION); 969 builder.append(":"); 970 builder.append(mPeriodCount); 971 builder.append(":"); 972 for(int i = 0; i < mPeriodCount; i++) { 973 builder.append(mPeriodRxData[i]); 974 builder.append(":"); 975 } 976 for(int i = 0; i < mPeriodCount; i++) { 977 builder.append(mPeriodTxData[i]); 978 builder.append(":"); 979 } 980 builder.append(mCurrentPeriod); 981 builder.append(":"); 982 builder.append(mPeriodStart.getTimeInMillis()); 983 builder.append(":"); 984 builder.append(mPeriodEnd.getTimeInMillis()); 985 986 BufferedWriter out = null; 987 try { 988 out = new BufferedWriter(new FileWriter(getDataFile()), 256); 989 out.write(builder.toString()); 990 } catch (IOException e) { 991 Slog.e(TAG, "Error writing data file"); 992 return; 993 } finally { 994 if (out != null) { 995 try { 996 out.close(); 997 } catch (Exception e) {} 998 } 999 } 1000 } 1001 1002 private void retrieve() { 1003 // clean out any old data first. If we fail to read we don't want old stuff 1004 zeroData(0); 1005 1006 File f = getDataFile(); 1007 byte[] buffer; 1008 FileInputStream s = null; 1009 try { 1010 buffer = new byte[(int)f.length()]; 1011 s = new FileInputStream(f); 1012 s.read(buffer); 1013 } catch (IOException e) { 1014 Slog.e(TAG, "Error reading data file"); 1015 return; 1016 } finally { 1017 if (s != null) { 1018 try { 1019 s.close(); 1020 } catch (Exception e) {} 1021 } 1022 } 1023 String data = new String(buffer); 1024 if (data == null || data.length() == 0) { 1025 if (DBG) Slog.d(TAG, "data file empty"); 1026 return; 1027 } 1028 synchronized (mParent) { 1029 String[] parsed = data.split(":"); 1030 int parsedUsed = 0; 1031 if (parsed.length < 6) { 1032 Slog.e(TAG, "reading data file with insufficient length - ignoring"); 1033 return; 1034 } 1035 1036 if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) { 1037 Slog.e(TAG, "reading data file with bad version - ignoring"); 1038 return; 1039 } 1040 1041 mPeriodCount = Integer.parseInt(parsed[parsedUsed++]); 1042 if (parsed.length != 5 + (2 * mPeriodCount)) { 1043 Slog.e(TAG, "reading data file with bad length (" + parsed.length + 1044 " != " + (5+(2*mPeriodCount)) + ") - ignoring"); 1045 return; 1046 } 1047 1048 mPeriodRxData = new long[mPeriodCount]; 1049 for(int i = 0; i < mPeriodCount; i++) { 1050 mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]); 1051 } 1052 mPeriodTxData = new long[mPeriodCount]; 1053 for(int i = 0; i < mPeriodCount; i++) { 1054 mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]); 1055 } 1056 mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]); 1057 mPeriodStart = new GregorianCalendar(); 1058 mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); 1059 mPeriodEnd = new GregorianCalendar(); 1060 mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++])); 1061 } 1062 } 1063 1064 long getPeriodRx(int which) { 1065 synchronized (mParent) { 1066 if (which > mPeriodCount) return 0; 1067 which = mCurrentPeriod - which; 1068 if (which < 0) which += mPeriodCount; 1069 return mPeriodRxData[which]; 1070 } 1071 } 1072 long getPeriodTx(int which) { 1073 synchronized (mParent) { 1074 if (which > mPeriodCount) return 0; 1075 which = mCurrentPeriod - which; 1076 if (which < 0) which += mPeriodCount; 1077 return mPeriodTxData[which]; 1078 } 1079 } 1080 } 1081 1082 @Override 1083 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1084 if (mContext.checkCallingOrSelfPermission( 1085 android.Manifest.permission.DUMP) 1086 != PackageManager.PERMISSION_GRANTED) { 1087 pw.println("Permission Denial: can't dump ThrottleService " + 1088 "from from pid=" + Binder.getCallingPid() + ", uid=" + 1089 Binder.getCallingUid()); 1090 return; 1091 } 1092 pw.println(); 1093 1094 pw.println("The threshold is " + mPolicyThreshold + 1095 ", after which you experince throttling to " + 1096 mPolicyThrottleValue + "kbps"); 1097 pw.println("Current period is " + 1098 (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " + 1099 "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 + 1100 " seconds."); 1101 pw.println("Polling every " + mPolicyPollPeriodSec + " seconds"); 1102 pw.println("Current Throttle Index is " + mThrottleIndex); 1103 pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec); 1104 1105 for (int i = 0; i < mRecorder.getPeriodCount(); i++) { 1106 pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" + 1107 mRecorder.getPeriodTx(i)); 1108 } 1109 } 1110} 1111