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