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