am 7171ea81: Make ThrottleService more tamper resistant.

Merge commit '7171ea8179e09270e4d6ab825a2320816eee39c5' into froyo-plus-aosp

* commit '7171ea8179e09270e4d6ab825a2320816eee39c5':
  Make ThrottleService more tamper resistant.
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index 7bc222e..9838fd2 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.IThrottleManager;
+import android.net.SntpClient;
 import android.net.ThrottleManager;
 import android.os.Binder;
 import android.os.Environment;
@@ -47,6 +48,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.telephony.TelephonyProperties;
 
 import java.io.BufferedWriter;
@@ -58,6 +60,7 @@
 import java.io.PrintWriter;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.Properties;
 import java.util.Random;
 
 // TODO - add comments - reference the ThrottleManager for public API
@@ -73,7 +76,7 @@
     private Context mContext;
 
     private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1;
-    private static final int TESTING_RESET_PERIOD_SEC = 60 * 3;
+    private static final int TESTING_RESET_PERIOD_SEC = 60 * 10;
     private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
 
     private static final int PERIOD_COUNT = 6;
@@ -114,10 +117,16 @@
     private static final int THROTTLE_INDEX_UNINITIALIZED = -1;
     private static final int THROTTLE_INDEX_UNTHROTTLED   =  0;
 
+    private static final String PROPERTIES_FILE = "/etc/gps.conf";
+    private String mNtpServer;
+    private boolean mNtpActive;
+
     public ThrottleService(Context context) {
         if (DBG) Slog.d(TAG, "Starting ThrottleService");
         mContext = context;
 
+        mNtpActive = false;
+
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent pollIntent = new Intent(ACTION_POLL, null);
         mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
@@ -169,21 +178,33 @@
     }
 
     // TODO - fetch for the iface
+    // return time in the local, system wall time, correcting for the use of ntp
     public synchronized long getResetTime(String iface) {
         enforceAccessPermission();
+        long resetTime = 0;
         if (mRecorder != null) {
-            mRecorder.getPeriodEnd();
+            long bestEnd = mRecorder.getPeriodEnd();
+            long bestNow = getBestTime();
+            long localNow = System.currentTimeMillis();
+
+            resetTime = localNow + (bestEnd - bestNow);
         }
-        return 0;
+        return resetTime;
     }
 
     // TODO - fetch for the iface
+    // return time in the loca, system wall tiem, correcting for the use of ntp
     public synchronized long getPeriodStartTime(String iface) {
         enforceAccessPermission();
+        long startTime = 0;
         if (mRecorder != null) {
-            mRecorder.getPeriodStart();
+            long bestStart = mRecorder.getPeriodStart();
+            long bestNow = getBestTime();
+            long localNow = System.currentTimeMillis();
+
+            startTime = localNow + (bestStart - bestNow);
         }
-        return 0;
+        return startTime;
     }
     //TODO - a better name?  getCliffByteCountThreshold?
     // TODO - fetch for the iface
@@ -257,6 +278,23 @@
 
         mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
         mSettingsObserver.observe(mContext);
+
+        FileInputStream stream = null;
+        try {
+            Properties properties = new Properties();
+            File file = new File(PROPERTIES_FILE);
+            stream = new FileInputStream(file);
+            properties.load(stream);
+            mNtpServer = properties.getProperty("NTP_SERVER", null);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (Exception e) {}
+            }
+        }
     }
 
 
@@ -297,13 +335,6 @@
 
             // get policy
             mHandler.obtainMessage(EVENT_POLICY_CHANGED).sendToTarget();
-
-            // evaluate current conditions
-            mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
-        }
-
-        private void onSimChange() {
-            // TODO
         }
 
         // check for new policy info (threshold limit/value/etc)
@@ -311,15 +342,15 @@
             boolean testing = SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true");
 
             int pollingPeriod = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_datause_polling_period_sec);
+                    R.integer.config_datause_polling_period_sec);
             mPolicyPollPeriodSec = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.THROTTLE_POLLING_SEC, pollingPeriod);
 
             // TODO - remove testing stuff?
             long defaultThreshold = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_datause_threshold_bytes);
+                    R.integer.config_datause_threshold_bytes);
             int defaultValue = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_datause_throttle_kbitsps);
+                    R.integer.config_datause_throttle_kbitsps);
             synchronized (ThrottleService.this) {
                 mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(),
                         Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
@@ -340,8 +371,7 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay);
             }
-            mIface = mContext.getResources().getString(
-                    com.android.internal.R.string.config_datause_iface);
+            mIface = mContext.getResources().getString(R.string.config_datause_iface);
             synchronized (ThrottleService.this) {
                 if (mIface == null) {
                     mPolicyThreshold = 0;
@@ -349,7 +379,7 @@
             }
 
             int defaultNotificationType = mContext.getResources().getInteger(
-                    com.android.internal.R.integer.config_datause_notification_type);
+                    R.integer.config_datause_notification_type);
             mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType);
 
@@ -369,6 +399,9 @@
         private void onPollAlarm() {
             long now = SystemClock.elapsedRealtime();
             long next = now + mPolicyPollPeriodSec*1000;
+
+            checkForAuthoritativeTime();
+
             long incRead = 0;
             long incWrite = 0;
             try {
@@ -407,8 +440,8 @@
             Intent broadcast = new Intent(ThrottleManager.THROTTLE_POLL_ACTION);
             broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_READ, periodRx);
             broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx);
-            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, mRecorder.getPeriodStart());
-            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, mRecorder.getPeriodEnd());
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, ThrottleService.this.getPeriodStartTime(mIface));
+            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, ThrottleService.this.getResetTime(mIface));
             mContext.sendStickyBroadcast(broadcast);
 
             mAlarmManager.cancel(mPendingPollIntent);
@@ -417,8 +450,15 @@
 
         private void checkThrottleAndPostNotification(long currentTotal) {
             // is throttling enabled?
-            if (mPolicyThreshold == 0)
+            if (mPolicyThreshold == 0) {
                 return;
+            }
+
+            // have we spoken with an ntp server yet?
+            // this is controversial, but we'd rather err towards not throttling
+            if ((mNtpServer != null) && !mNtpActive) {
+                return;
+            }
 
             // check if we need to throttle
             if (currentTotal > mPolicyThreshold) {
@@ -434,12 +474,11 @@
                         Slog.e(TAG, "error setting Throttle: " + e);
                     }
 
-                    mNotificationManager.cancel(com.android.internal.R.drawable.
-                            stat_sys_throttled);
+                    mNotificationManager.cancel(R.drawable.stat_sys_throttled);
 
-                    postNotification(com.android.internal.R.string.throttled_notification_title,
-                            com.android.internal.R.string.throttled_notification_message,
-                            com.android.internal.R.drawable.stat_sys_throttled,
+                    postNotification(R.string.throttled_notification_title,
+                            R.string.throttled_notification_message,
+                            R.drawable.stat_sys_throttled,
                             Notification.FLAG_ONGOING_EVENT);
 
                     Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
@@ -469,19 +508,15 @@
                     if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) {
                         if (mWarningNotificationSent == false) {
                             mWarningNotificationSent = true;
-                            mNotificationManager.cancel(com.android.internal.R.drawable.
-                                    stat_sys_throttled);
-                            postNotification(com.android.internal.R.string.
-                                    throttle_warning_notification_title,
-                                    com.android.internal.R.string.
-                                    throttle_warning_notification_message,
-                                    com.android.internal.R.drawable.stat_sys_throttled,
+                            mNotificationManager.cancel(R.drawable.stat_sys_throttled);
+                            postNotification(R.string.throttle_warning_notification_title,
+                                    R.string.throttle_warning_notification_message,
+                                    R.drawable.stat_sys_throttled,
                                     0);
                         }
                     } else {
                         if (mWarningNotificationSent == true) {
-                            mNotificationManager.cancel(com.android.internal.R.drawable.
-                                    stat_sys_throttled);
+                            mNotificationManager.cancel(R.drawable.stat_sys_throttled);
                             mWarningNotificationSent =false;
                         }
                     }
@@ -529,12 +564,13 @@
                 broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1);
                 mContext.sendStickyBroadcast(broadcast);
             }
-            mNotificationManager.cancel(com.android.internal.R.drawable.stat_sys_throttled);
+            mNotificationManager.cancel(R.drawable.stat_sys_throttled);
             mWarningNotificationSent = false;
         }
 
-        private Calendar calculatePeriodEnd() {
+        private Calendar calculatePeriodEnd(long now) {
             Calendar end = GregorianCalendar.getInstance();
+            end.setTimeInMillis(now);
             int day = end.get(Calendar.DAY_OF_MONTH);
             end.set(Calendar.DAY_OF_MONTH, mPolicyResetDay);
             end.set(Calendar.HOUR_OF_DAY, 0);
@@ -553,6 +589,7 @@
             // TODO - remove!
             if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
                 end = GregorianCalendar.getInstance();
+                end.setTimeInMillis(now);
                 end.add(Calendar.SECOND, TESTING_RESET_PERIOD_SEC);
             }
             return end;
@@ -580,19 +617,59 @@
                         " bytes read and " + mRecorder.getPeriodTx(0) + " written");
             }
 
-            Calendar end = calculatePeriodEnd();
-            Calendar start = calculatePeriodStart(end);
+            long now = getBestTime();
 
-            clearThrottleAndNotification();
+            if (mNtpActive || (mNtpServer == null)) {
+                Calendar end = calculatePeriodEnd(now);
+                Calendar start = calculatePeriodStart(end);
 
-            mRecorder.setNextPeriod(start,end);
+                if (mRecorder.setNextPeriod(start, end)) {
+                    clearThrottleAndNotification();
+                }
 
-            mAlarmManager.cancel(mPendingResetIntent);
-            mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(),
-                    mPendingResetIntent);
+                mAlarmManager.cancel(mPendingResetIntent);
+                long offset = end.getTimeInMillis() - now;
+                // use Elapsed realtime so clock changes don't fool us.
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        SystemClock.elapsedRealtime() + offset,
+                        mPendingResetIntent);
+            } else {
+                if (DBG) Slog.d(TAG, "no authoritative time - not resetting period");
+            }
         }
     }
 
+    private void checkForAuthoritativeTime() {
+        if (mNtpActive || (mNtpServer == null)) return;
+
+        SntpClient client = new SntpClient();
+        if (client.requestTime(mNtpServer, 10000)) {
+            mNtpActive = true;
+            if (DBG) Slog.d(TAG, "found Authoritative time - reseting alarm");
+            mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
+        }
+    }
+
+    private long getBestTime() {
+        SntpClient client = new SntpClient();
+
+        long time;
+        if ((mNtpServer != null) && client.requestTime(mNtpServer, 10000)) {
+            time = client.getNtpTime() ;
+            if (!mNtpActive) {
+                mNtpActive = true;
+                if (DBG) Slog.d(TAG, "found Authoritative time - reseting alarm");
+                mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
+            }
+            if (DBG) Slog.d(TAG, "using Authoritative time: " + time);
+        } else {
+            time = System.currentTimeMillis();
+            if (DBG) Slog.d(TAG, "using User time: " + time);
+            mNtpActive = false;
+        }
+        return time;
+    }
+
     // records bytecount data for a given time and accumulates it into larger time windows
     // for logging and other purposes
     //
@@ -633,16 +710,16 @@
             }
         }
 
-        void setNextPeriod(Calendar start, Calendar end) {
+        boolean setNextPeriod(Calendar start, Calendar end) {
             // TODO - how would we deal with a dual-IMSI device?
             checkForSubscriberId();
+            boolean startNewPeriod = true;
             if (DBG) {
                 Slog.d(TAG, "setting next period to " + start.getTimeInMillis() +
                         " --until-- " + end.getTimeInMillis());
             }
-            // if we roll back in time to a previous period, toss out the current data
-            // if we roll forward to the next period, advance to the next
-
+            // if we rolled back in time, toss out
+            // if we rolled foward, advance to the next
             if (end.before(mPeriodStart)) {
                 if (DBG) {
                     Slog.d(TAG, " old start was " + mPeriodStart.getTimeInMillis() + ", wiping");
@@ -662,11 +739,13 @@
                     mPeriodTxData[mCurrentPeriod] = 0;
                 }
             } else {
+                startNewPeriod = false;
                 if (DBG) Slog.d(TAG, " we fit - ammending to last period");
             }
             setPeriodStart(start);
             setPeriodEnd(end);
             record();
+            return startNewPeriod;
         }
 
         public long getPeriodEnd() {
@@ -714,6 +793,7 @@
         // otherwise time moved forward.
         void addData(long bytesRead, long bytesWritten) {
             checkForSubscriberId();
+
             synchronized (mParent) {
                 mPeriodRxData[mCurrentPeriod] += bytesRead;
                 mPeriodTxData[mCurrentPeriod] += bytesWritten;
@@ -817,11 +897,10 @@
             builder.append(mPeriodStart.getTimeInMillis());
             builder.append(":");
             builder.append(mPeriodEnd.getTimeInMillis());
-            builder.append(":");
 
             BufferedWriter out = null;
             try {
-                out = new BufferedWriter(new FileWriter(getDataFile()),256);
+                out = new BufferedWriter(new FileWriter(getDataFile()), 256);
                 out.write(builder.toString());
             } catch (IOException e) {
                 Slog.e(TAG, "Error writing data file");
@@ -854,7 +933,10 @@
                 }
             }
             String data = new String(buffer);
-            if (data == null || data.length() == 0) return;
+            if (data == null || data.length() == 0) {
+                if (DBG) Slog.d(TAG, "data file empty");
+                return;
+            }
             synchronized (mParent) {
                 String[] parsed = data.split(":");
                 int parsedUsed = 0;
@@ -869,7 +951,10 @@
                 }
 
                 mPeriodCount = Integer.parseInt(parsed[parsedUsed++]);
-                if (parsed.length != 4 + (2 * mPeriodCount)) return;
+                if (parsed.length != 5 + (2 * mPeriodCount)) {
+                    Slog.e(TAG, "reading data file with bad length ("+parsed.length+" != "+(4 + (2*mPeriodCount))+") - ignoring");
+                    return;
+                }
 
                 mPeriodRxData = new long[mPeriodCount];
                 for(int i = 0; i < mPeriodCount; i++) {
@@ -922,7 +1007,7 @@
                 mPolicyThrottleValue + "kbps");
         pw.println("Current period is " +
                 (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
-                "and ends in " + (mRecorder.getPeriodEnd() - System.currentTimeMillis()) / 1000 +
+                "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 +
                 " seconds.");
         pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
         pw.println("Current Throttle Index is " + mThrottleIndex);