ThrottleService.java revision 9e696c29f06d45d2891e1d38fd8d9033a9e21bb9
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.app.Service;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.PackageManager;
29import android.content.res.Resources;
30import android.content.SharedPreferences;
31import android.net.IThrottleManager;
32import android.net.ThrottleManager;
33import android.os.Binder;
34import android.os.Handler;
35import android.os.HandlerThread;
36import android.os.IBinder;
37import android.os.INetworkManagementService;
38import android.os.Looper;
39import android.os.Message;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.SystemClock;
43import android.os.SystemProperties;
44import android.provider.Settings;
45import android.util.Slog;
46
47import java.io.FileDescriptor;
48import java.io.PrintWriter;
49import java.util.Calendar;
50import java.util.GregorianCalendar;
51import java.util.Random;
52
53// TODO - add comments - reference the ThrottleManager for public API
54public class ThrottleService extends IThrottleManager.Stub {
55
56    private static final String TESTING_ENABLED_PROPERTY = "persist.throttle.testing";
57
58    private static final String TAG = "ThrottleService";
59    private static boolean DBG = true;
60    private Handler mHandler;
61    private HandlerThread mThread;
62
63    private Context mContext;
64
65    private int mPolicyPollPeriodSec;
66    private static final int DEFAULT_POLLING_PERIOD_SEC = 60 * 10;
67    private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1;
68
69    private static final int TESTING_RESET_PERIOD_SEC = 60 * 3;
70
71    private static final int PERIOD_COUNT = 6;
72
73    private long mPolicyThreshold;
74    // TODO - remove testing stuff?
75    private static final long DEFAULT_TESTING_THRESHOLD = 1 * 1024 * 1024;
76    private static final long DEFAULT_THRESHOLD = 0; // off by default
77
78    private int mPolicyThrottleValue;
79    private static final int DEFAULT_THROTTLE_VALUE = 100; // 100 Kbps
80
81    private int mPolicyResetDay; // 1-28
82
83    private long mLastRead; // read byte count from last poll
84    private long mLastWrite; // write byte count from last poll
85
86    private static final String ACTION_POLL = "com.android.server.ThrottleManager.action.POLL";
87    private static int POLL_REQUEST = 0;
88    private PendingIntent mPendingPollIntent;
89    private static final String ACTION_RESET = "com.android.server.ThorottleManager.action.RESET";
90    private static int RESET_REQUEST = 1;
91    private PendingIntent mPendingResetIntent;
92
93    private INetworkManagementService mNMService;
94    private AlarmManager mAlarmManager;
95    private NotificationManager mNotificationManager;
96
97    private DataRecorder mRecorder;
98
99    private int mThrottleLevel; // 0 for none, 1 for first throttle val, 2 for next, etc
100
101    private String mPolicyIface;
102
103    private static final int NOTIFICATION_WARNING   = 2;
104    private static final int NOTIFICATION_ALL       = 0xFFFFFFFF;
105    private int mPolicyNotificationsAllowedMask;
106
107    private Notification mThrottlingNotification;
108    private boolean mWarningNotificationSent = false;
109
110    public ThrottleService(Context context) {
111        if (DBG) Slog.d(TAG, "Starting ThrottleService");
112        mContext = context;
113
114        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
115        Intent pollIntent = new Intent(ACTION_POLL, null);
116        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
117        Intent resetIntent = new Intent(ACTION_RESET, null);
118        mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0);
119
120        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
121        mNMService = INetworkManagementService.Stub.asInterface(b);
122
123        mNotificationManager = (NotificationManager)mContext.getSystemService(
124                Context.NOTIFICATION_SERVICE);
125    }
126
127    private void enforceAccessPermission() {
128        mContext.enforceCallingOrSelfPermission(
129                android.Manifest.permission.ACCESS_NETWORK_STATE,
130                "ThrottleService");
131    }
132
133    public synchronized long getResetTime(String iface) {
134        enforceAccessPermission();
135        if (iface.equals(mPolicyIface) && (mRecorder != null)) mRecorder.getPeriodEnd();
136        return 0;
137    }
138    public synchronized long getPeriodStartTime(String iface) {
139        enforceAccessPermission();
140        if (iface.equals(mPolicyIface) && (mRecorder != null)) mRecorder.getPeriodStart();
141        return 0;
142    }
143    //TODO - a better name?  getCliffByteCountThreshold?
144    public synchronized long getCliffThreshold(String iface, int cliff) {
145        enforceAccessPermission();
146        if ((cliff == 0) && iface.equals(mPolicyIface)) {
147            return mPolicyThreshold;
148        }
149        return 0;
150    }
151    // TODO - a better name? getThrottleRate?
152    public synchronized int getCliffLevel(String iface, int cliff) {
153        enforceAccessPermission();
154        if ((cliff == 0) && iface.equals(mPolicyIface)) {
155            return mPolicyThrottleValue;
156        }
157        return 0;
158    }
159
160    public synchronized long getByteCount(String iface, int dir, int period, int ago) {
161        enforceAccessPermission();
162        if (iface.equals(mPolicyIface) &&
163                (period == ThrottleManager.PERIOD_CYCLE) &&
164                (mRecorder != null)) {
165            if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago);
166            if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago);
167        }
168        return 0;
169    }
170
171    // TODO - a better name - getCurrentThrottleRate?
172    public synchronized int getThrottle(String iface) {
173        enforceAccessPermission();
174        if (iface.equals(mPolicyIface) && (mThrottleLevel == 1)) {
175            return mPolicyThrottleValue;
176        }
177        return 0;
178    }
179
180    void systemReady() {
181        if (DBG) Slog.d(TAG, "systemReady");
182        mContext.registerReceiver(
183            new BroadcastReceiver() {
184                @Override
185                public void onReceive(Context context, Intent intent) {
186                    mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
187                }
188            }, new IntentFilter(ACTION_POLL));
189
190        mContext.registerReceiver(
191            new BroadcastReceiver() {
192                @Override
193                public void onReceive(Context context, Intent intent) {
194                    mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
195                }
196            }, new IntentFilter(ACTION_RESET));
197
198        // use a new thread as we don't want to stall the system for file writes
199        mThread = new HandlerThread(TAG);
200        mThread.start();
201        mHandler = new MyHandler(mThread.getLooper());
202        mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
203    }
204
205
206    private static final int EVENT_REBOOT_RECOVERY = 0;
207    private static final int EVENT_POLICY_CHANGED  = 1;
208    private static final int EVENT_POLL_ALARM      = 2;
209    private static final int EVENT_RESET_ALARM     = 3;
210    private class MyHandler extends Handler {
211        public MyHandler(Looper l) {
212            super(l);
213        }
214
215        @Override
216        public void handleMessage(Message msg) {
217            switch (msg.what) {
218            case EVENT_REBOOT_RECOVERY:
219                onRebootRecovery();
220                break;
221            case EVENT_POLICY_CHANGED:
222                onPolicyChanged();
223                break;
224            case EVENT_POLL_ALARM:
225                onPollAlarm();
226                break;
227            case EVENT_RESET_ALARM:
228                onResetAlarm();
229            }
230        }
231
232        private void onRebootRecovery() {
233            if (DBG) Slog.d(TAG, "onRebootRecovery");
234            // check for sim change TODO
235            // reregister for notification of policy change
236
237            // register for roaming indication change
238            // check for roaming TODO
239
240            mRecorder = new DataRecorder(mContext, ThrottleService.this);
241
242            // get policy
243            mHandler.obtainMessage(EVENT_POLICY_CHANGED).sendToTarget();
244
245            // evaluate current conditions
246            mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
247        }
248
249        private void onSimChange() {
250            // TODO
251        }
252
253        // check for new policy info (threshold limit/value/etc)
254        private void onPolicyChanged() {
255            boolean testing = SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true");
256
257            int pollingPeriod = DEFAULT_POLLING_PERIOD_SEC;
258            if (testing) pollingPeriod = TESTING_POLLING_PERIOD_SEC;
259            mPolicyPollPeriodSec = Settings.Secure.getInt(mContext.getContentResolver(),
260                    Settings.Secure.THROTTLE_POLLING_SEC, pollingPeriod);
261
262            // TODO - remove testing stuff?
263            long defaultThreshold = DEFAULT_THRESHOLD;
264            if (testing) defaultThreshold = DEFAULT_TESTING_THRESHOLD;
265            synchronized (ThrottleService.this) {
266                mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(),
267                        Settings.Secure.THROTTLE_THRESHOLD, defaultThreshold);
268                mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(),
269                        Settings.Secure.THROTTLE_VALUE, DEFAULT_THROTTLE_VALUE);
270            }
271            mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(),
272                    Settings.Secure.THROTTLE_RESET_DAY, -1);
273            if (mPolicyResetDay == -1 ||
274                    ((mPolicyResetDay < 1) || (mPolicyResetDay > 28))) {
275                Random g = new Random();
276                mPolicyResetDay = 1 + g.nextInt(28); // 1-28
277                Settings.Secure.putInt(mContext.getContentResolver(),
278                Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay);
279            }
280            synchronized (ThrottleService.this) {
281                mPolicyIface = Settings.Secure.getString(mContext.getContentResolver(),
282                        Settings.Secure.THROTTLE_IFACE);
283                // TODO - read default from resource so it's device-specific
284                if (mPolicyIface == null) mPolicyIface = "rmnet0";
285            }
286
287            mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(),
288                    Settings.Secure.THROTTLE_NOTIFICATION_TYPE, NOTIFICATION_ALL);
289
290            Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + mPolicyPollPeriodSec +
291                    ", threshold=" + mPolicyThreshold + ", value=" + mPolicyThrottleValue +
292                    ", resetDay=" + mPolicyResetDay + ", noteType=" +
293                    mPolicyNotificationsAllowedMask);
294
295            Calendar end = calculatePeriodEnd();
296            Calendar start = calculatePeriodStart(end);
297
298            mRecorder.setNextPeriod(start,end);
299
300            mAlarmManager.cancel(mPendingResetIntent);
301            mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(),
302                    mPendingResetIntent);
303        }
304
305        private void onPollAlarm() {
306            long now = SystemClock.elapsedRealtime();
307            long next = now + mPolicyPollPeriodSec*1000;
308            long incRead = 0;
309            long incWrite = 0;
310            try {
311                incRead = mNMService.getInterfaceRxCounter(mPolicyIface) - mLastRead;
312                incWrite = mNMService.getInterfaceTxCounter(mPolicyIface) - mLastWrite;
313            } catch (RemoteException e) {
314                Slog.e(TAG, "got remoteException in onPollAlarm:" + e);
315            }
316
317            mRecorder.addData(incRead, incWrite);
318
319            long periodRx = mRecorder.getPeriodRx(0);
320            long periodTx = mRecorder.getPeriodTx(0);
321            long total = periodRx + periodTx;
322            if (DBG) {
323                Slog.d(TAG, "onPollAlarm - now =" + now + ", read =" + incRead +
324                        ", written =" + incWrite + ", new total =" + total);
325            }
326            mLastRead += incRead;
327            mLastWrite += incWrite;
328
329            checkThrottleAndPostNotification(total);
330
331            Intent broadcast = new Intent(ThrottleManager.THROTTLE_POLL_ACTION);
332            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_READ, periodRx);
333            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx);
334            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, mRecorder.getPeriodStart());
335            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, mRecorder.getPeriodEnd());
336            mContext.sendStickyBroadcast(broadcast);
337
338            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, next, mPendingPollIntent);
339        }
340
341        private void checkThrottleAndPostNotification(long currentTotal) {
342            // are we even doing this?
343            if (mPolicyThreshold == 0)
344                return;
345
346            // check if we need to throttle
347            if (currentTotal > mPolicyThreshold) {
348                if (mThrottleLevel != 1) {
349                    synchronized (ThrottleService.this) {
350                        mThrottleLevel = 1;
351                    }
352                    if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!");
353                    try {
354                        mNMService.setInterfaceThrottle(mPolicyIface,
355                                mPolicyThrottleValue, mPolicyThrottleValue);
356                    } catch (Exception e) {
357                        Slog.e(TAG, "error setting Throttle: " + e);
358                    }
359
360                    mNotificationManager.cancel(com.android.internal.R.drawable.
361                            stat_sys_throttle_warning);
362
363                    postNotification(com.android.internal.R.string.throttled_notification_title,
364                            com.android.internal.R.string.throttled_notification_message,
365                            com.android.internal.R.drawable.stat_sys_throttled);
366
367                    Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
368                    broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue);
369                    mContext.sendStickyBroadcast(broadcast);
370
371                } // else already up!
372            } else {
373                if ((mPolicyNotificationsAllowedMask & NOTIFICATION_WARNING) != 0) {
374                    // check if we should warn about throttle
375                    if (currentTotal > (mPolicyThreshold/2) && !mWarningNotificationSent) {
376                        mWarningNotificationSent = true;
377                        mNotificationManager.cancel(com.android.internal.R.drawable.
378                                stat_sys_throttle_warning);
379                        postNotification(com.android.internal.R.string.
380                                throttle_warning_notification_title,
381                                com.android.internal.R.string.
382                                throttle_warning_notification_message,
383                                com.android.internal.R.drawable.stat_sys_throttle_warning);
384                    } else {
385                        mWarningNotificationSent =false;
386                    }
387                }
388            }
389        }
390
391        private void postNotification(int titleInt, int messageInt, int icon) {
392            Intent intent = new Intent();
393            // TODO - fix up intent
394            intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
395            intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
396
397            PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
398
399            Resources r = Resources.getSystem();
400            CharSequence title = r.getText(titleInt);
401            CharSequence message = r.getText(messageInt);
402            if (mThrottlingNotification == null) {
403                mThrottlingNotification = new Notification();
404                mThrottlingNotification.when = 0;
405                // TODO -  fixup icon
406                mThrottlingNotification.icon = icon;
407                mThrottlingNotification.defaults &= ~Notification.DEFAULT_SOUND;
408//                mThrottlingNotification.flags = Notification.FLAG_ONGOING_EVENT;
409            }
410            mThrottlingNotification.tickerText = title;
411            mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi);
412
413            mNotificationManager.notify(mThrottlingNotification.icon, mThrottlingNotification);
414        }
415
416
417        private synchronized void clearThrottleAndNotification() {
418            if (mThrottleLevel == 1) {
419                synchronized (ThrottleService.this) {
420                    mThrottleLevel = 0;
421                }
422                try {
423                    mNMService.setInterfaceThrottle(mPolicyIface, -1, -1);
424                } catch (Exception e) {
425                    Slog.e(TAG, "error clearing Throttle: " + e);
426                }
427                Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
428                broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1);
429                mContext.sendStickyBroadcast(broadcast);
430            }
431            mNotificationManager.cancel(com.android.internal.R.drawable.stat_sys_throttle_warning);
432            mNotificationManager.cancel(com.android.internal.R.drawable.stat_sys_throttled);
433            mWarningNotificationSent = false;
434        }
435
436        private Calendar calculatePeriodEnd() {
437            Calendar end = GregorianCalendar.getInstance();
438            int day = end.get(Calendar.DAY_OF_MONTH);
439            end.set(Calendar.DAY_OF_MONTH, mPolicyResetDay);
440            end.set(Calendar.HOUR_OF_DAY, 0);
441            end.set(Calendar.MINUTE, 0);
442            if (day >= mPolicyResetDay) {
443                int month = end.get(Calendar.MONTH);
444                if (month == Calendar.DECEMBER) {
445                    end.set(Calendar.YEAR, end.get(Calendar.YEAR) + 1);
446                    month = Calendar.JANUARY - 1;
447                }
448                end.set(Calendar.MONTH, month + 1);
449            }
450
451            // TODO - remove!
452            if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
453                end = GregorianCalendar.getInstance();
454                end.add(Calendar.SECOND, TESTING_RESET_PERIOD_SEC);
455            }
456            return end;
457        }
458        private Calendar calculatePeriodStart(Calendar end) {
459            Calendar start = (Calendar)end.clone();
460            int month = end.get(Calendar.MONTH);
461            if (end.get(Calendar.MONTH) == Calendar.JANUARY) {
462                month = Calendar.DECEMBER + 1;
463                start.set(Calendar.YEAR, start.get(Calendar.YEAR) - 1);
464            }
465            start.set(Calendar.MONTH, month - 1);
466
467            // TODO - remove!!
468            if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
469                start = (Calendar)end.clone();
470                start.add(Calendar.SECOND, -TESTING_RESET_PERIOD_SEC);
471            }
472            return start;
473        }
474
475        private void onResetAlarm() {
476            if (DBG) {
477                Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) +
478                        " bytes read and " + mRecorder.getPeriodTx(0) + " written");
479            }
480
481            Calendar end = calculatePeriodEnd();
482            Calendar start = calculatePeriodStart(end);
483
484            clearThrottleAndNotification();
485
486            mRecorder.setNextPeriod(start,end);
487
488            mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(),
489                    mPendingResetIntent);
490        }
491    }
492
493    // records bytecount data for a given time and accumulates it into larger time windows
494    // for logging and other purposes
495    //
496    // since time can be changed (user or network action) we will have to track the time of the
497    // last recording and deal with it.
498    private static class DataRecorder {
499        long[] mPeriodRxData;
500        long[] mPeriodTxData;
501        int mCurrentPeriod;
502        int mPeriodCount;
503
504        Calendar mPeriodStart;
505        Calendar mPeriodEnd;
506
507        ThrottleService mParent;
508        Context mContext;
509        SharedPreferences mSharedPreferences;
510
511        DataRecorder(Context context, ThrottleService parent) {
512            mContext = context;
513            mParent = parent;
514
515            synchronized (mParent) {
516                mPeriodCount = 6;
517                mPeriodRxData = new long[mPeriodCount];
518                mPeriodTxData = new long[mPeriodCount];
519
520                mPeriodStart = Calendar.getInstance();
521                mPeriodEnd = Calendar.getInstance();
522
523                mSharedPreferences = mContext.getSharedPreferences("ThrottleData",
524                        android.content.Context.MODE_PRIVATE);
525
526                zeroData(0);
527                retrieve();
528            }
529        }
530
531        void setNextPeriod(Calendar start, Calendar end) {
532            if (DBG) {
533                Slog.d(TAG, "setting next period to " + start.getTimeInMillis() +
534                        " --until-- " + end.getTimeInMillis());
535            }
536            // if we roll back in time to a previous period, toss out the current data
537            // if we roll forward to the next period, advance to the next
538
539            if (end.before(mPeriodStart)) {
540                if (DBG) {
541                    Slog.d(TAG, " old start was " + mPeriodStart.getTimeInMillis() + ", wiping");
542                }
543                synchronized (mParent) {
544                    mPeriodRxData[mCurrentPeriod] = 0;
545                    mPeriodTxData[mCurrentPeriod] = 0;
546                }
547            } else if(start.after(mPeriodEnd)) {
548                if (DBG) {
549                    Slog.d(TAG, " old end was " + mPeriodEnd.getTimeInMillis() + ", following");
550                }
551                synchronized (mParent) {
552                    ++mCurrentPeriod;
553                    if (mCurrentPeriod >= mPeriodCount) mCurrentPeriod = 0;
554                    mPeriodRxData[mCurrentPeriod] = 0;
555                    mPeriodTxData[mCurrentPeriod] = 0;
556                }
557            } else {
558                if (DBG) Slog.d(TAG, " we fit - ammending to last period");
559            }
560            setPeriodStart(start);
561            setPeriodEnd(end);
562            record();
563        }
564
565        public long getPeriodEnd() {
566            synchronized (mParent) {
567                return mPeriodEnd.getTimeInMillis();
568            }
569        }
570
571        private void setPeriodEnd(Calendar end) {
572            synchronized (mParent) {
573                mPeriodEnd = end;
574            }
575        }
576
577        public long getPeriodStart() {
578            synchronized (mParent) {
579                return mPeriodStart.getTimeInMillis();
580            }
581        }
582
583        private void setPeriodStart(Calendar start) {
584            synchronized (mParent) {
585                mPeriodStart = start;
586            }
587        }
588
589        public int getPeriodCount() {
590            synchronized (mParent) {
591                return mPeriodCount;
592            }
593        }
594
595        private void zeroData(int field) {
596            synchronized (mParent) {
597                for(int period = 0; period<mPeriodCount; period++) {
598                    mPeriodRxData[period] = 0;
599                    mPeriodTxData[period] = 0;
600                }
601                mCurrentPeriod = 0;
602            }
603
604        }
605
606        // if time moves backward accumulate all read/write that's lost into the now
607        // otherwise time moved forward.
608        void addData(long bytesRead, long bytesWritten) {
609            synchronized (mParent) {
610                mPeriodRxData[mCurrentPeriod] += bytesRead;
611                mPeriodTxData[mCurrentPeriod] += bytesWritten;
612            }
613            record();
614        }
615
616        private void record() {
617            // serialize into a secure setting
618
619            // 1 int mPeriodCount
620            // 13*6 long[PERIOD_COUNT] mPeriodRxData
621            // 13*6 long[PERIOD_COUNT] mPeriodTxData
622            // 1  int mCurrentPeriod
623            // 13 long periodStartMS
624            // 13 long periodEndMS
625            // 199 chars max
626            StringBuilder builder = new StringBuilder();
627            builder.append(mPeriodCount);
628            builder.append(":");
629            for(int i=0; i < mPeriodCount; i++) {
630                builder.append(mPeriodRxData[i]);
631                builder.append(":");
632            }
633            for(int i=0; i < mPeriodCount; i++) {
634                builder.append(mPeriodTxData[i]);
635                builder.append(":");
636            }
637            builder.append(mCurrentPeriod);
638            builder.append(":");
639            builder.append(mPeriodStart.getTimeInMillis());
640            builder.append(":");
641            builder.append(mPeriodEnd.getTimeInMillis());
642            builder.append(":");
643
644            SharedPreferences.Editor editor = mSharedPreferences.edit();
645
646            editor.putString("Data", builder.toString());
647            editor.commit();
648        }
649
650        private void retrieve() {
651            String data = mSharedPreferences.getString("Data", "");
652//            String data = Settings.Secure.getString(mContext.getContentResolver(),
653//                    Settings.Secure.THROTTLE_VALUE);
654            if (data == null || data.length() == 0) return;
655
656            synchronized (mParent) {
657                String[] parsed = data.split(":");
658                int parsedUsed = 0;
659                if (parsed.length < 6) return;
660
661                mPeriodCount = Integer.parseInt(parsed[parsedUsed++]);
662                if (parsed.length != 4 + (2 * mPeriodCount)) return;
663
664                mPeriodRxData = new long[mPeriodCount];
665                for(int i=0; i < mPeriodCount; i++) {
666                    mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
667                }
668                mPeriodTxData = new long[mPeriodCount];
669                for(int i=0; i < mPeriodCount; i++) {
670                    mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
671                }
672                mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]);
673                mPeriodStart = new GregorianCalendar();
674                mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
675                mPeriodEnd = new GregorianCalendar();
676                mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
677            }
678        }
679
680        long getPeriodRx(int which) {
681            if (DBG) { // TODO - remove
682                Slog.d(TAG, "reading slot "+ which +" with current =" + mCurrentPeriod);
683                for(int x = 0; x<mPeriodCount; x++) {
684                    Slog.d(TAG, "  " + x + " = " + mPeriodRxData[x]);
685                }
686            }
687            synchronized (mParent) {
688                if (which > mPeriodCount) return 0;
689                which = mCurrentPeriod - which;
690                if (which < 0) which += mPeriodCount;
691                return mPeriodRxData[which];
692            }
693        }
694        long getPeriodTx(int which) {
695            synchronized (mParent) {
696                if (which > mPeriodCount) return 0;
697                which = mCurrentPeriod - which;
698                if (which < 0) which += mPeriodCount;
699                return mPeriodTxData[which];
700            }
701        }
702    }
703
704    @Override
705    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
706        if (mContext.checkCallingOrSelfPermission(
707                android.Manifest.permission.DUMP)
708                != PackageManager.PERMISSION_GRANTED) {
709            pw.println("Permission Denial: can't dump ThrottleService " +
710                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
711                    Binder.getCallingUid());
712            return;
713        }
714        pw.println();
715
716        pw.println("The threshold is " + mPolicyThreshold +
717                ", after which you experince throttling to " +
718                mPolicyThrottleValue + "kbps");
719        pw.println("Current period is " +
720                (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
721                "and ends in " + (mRecorder.getPeriodEnd() - System.currentTimeMillis()) / 1000 +
722                " seconds.");
723        pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
724        for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
725            pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" +
726                    mRecorder.getPeriodTx(i));
727        }
728    }
729}
730