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