Cached NTP time for system services.

Uses NTP server and timeout from secure settings, or fallback to
defaults in resources.  Update various system services to use cached
NTP time when fresh enough, or force updates as needed.

Bug: 4517273
Change-Id: Ie1c4c4883836013d02ca0bbd850cf8949f93b34b
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 65babc2..23b53ae 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3833,6 +3833,11 @@
         /** {@hide} */
         public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
 
+        /** Preferred NTP server. {@hide} */
+        public static final String NTP_SERVER = "ntp_server";
+        /** Timeout in milliseconds to wait for NTP server. {@hide} */
+        public static final String NTP_TIMEOUT = "ntp_timeout";
+
         /**
          * @hide
          */
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 5b19ecd..2179ff3 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,41 +16,71 @@
 
 package android.util;
 
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
 import android.net.SntpClient;
 import android.os.SystemClock;
+import android.provider.Settings;
 
 /**
- * {@link TrustedTime} that connects with a remote NTP server as its remote
- * trusted time source.
+ * {@link TrustedTime} that connects with a remote NTP server as its trusted
+ * time source.
  *
  * @hide
  */
 public class NtpTrustedTime implements TrustedTime {
-    private String mNtpServer;
-    private long mNtpTimeout;
+    private static final String TAG = "NtpTrustedTime";
+    private static final boolean LOGD = false;
+
+    private static NtpTrustedTime sSingleton;
+
+    private final String mServer;
+    private final long mTimeout;
 
     private boolean mHasCache;
     private long mCachedNtpTime;
     private long mCachedNtpElapsedRealtime;
     private long mCachedNtpCertainty;
 
-    public NtpTrustedTime() {
+    private NtpTrustedTime(String server, long timeout) {
+        if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
+        mServer = server;
+        mTimeout = timeout;
     }
 
-    public void setNtpServer(String server, long timeout) {
-        mNtpServer = server;
-        mNtpTimeout = timeout;
+    public static synchronized NtpTrustedTime getInstance(Context context) {
+        if (sSingleton == null) {
+            final Resources res = context.getResources();
+            final ContentResolver resolver = context.getContentResolver();
+
+            final String defaultServer = res.getString(
+                    com.android.internal.R.string.config_ntpServer);
+            final long defaultTimeout = res.getInteger(
+                    com.android.internal.R.integer.config_ntpTimeout);
+
+            final String secureServer = Settings.Secure.getString(
+                    resolver, Settings.Secure.NTP_SERVER);
+            final long timeout = Settings.Secure.getLong(
+                    resolver, Settings.Secure.NTP_TIMEOUT, defaultTimeout);
+
+            final String server = secureServer != null ? secureServer : defaultServer;
+            sSingleton = new NtpTrustedTime(server, timeout);
+        }
+
+        return sSingleton;
     }
 
     /** {@inheritDoc} */
     public boolean forceRefresh() {
-        if (mNtpServer == null) {
+        if (mServer == null) {
             // missing server, so no trusted time available
             return false;
         }
 
+        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
         final SntpClient client = new SntpClient();
-        if (client.requestTime(mNtpServer, (int) mNtpTimeout)) {
+        if (client.requestTime(mServer, (int) mTimeout)) {
             mHasCache = true;
             mCachedNtpTime = client.getNtpTime();
             mCachedNtpElapsedRealtime = client.getNtpTimeReference();
@@ -89,9 +119,19 @@
         if (!mHasCache) {
             throw new IllegalStateException("Missing authoritative time source");
         }
+        if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
 
         // current time is age after the last ntp cache; callers who
         // want fresh values will hit makeAuthoritative() first.
         return mCachedNtpTime + getCacheAge();
     }
+
+    public long getCachedNtpTime() {
+        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+        return mCachedNtpTime;
+    }
+
+    public long getCachedNtpTimeReference() {
+        return mCachedNtpElapsedRealtime;
+    }
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9f05cbc..1f2b7fb 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -660,4 +660,9 @@
          extremely limited. -->
     <bool name="config_allowActionMenuItemTextWithIcon">false</bool>
 
+    <!-- Remote server that can provide NTP responses. -->
+    <string translatable="false" name="config_ntpServer">pool.ntp.org</string>
+    <!-- Timeout to wait for NTP server response. -->
+    <integer name="config_ntpTimeout">20000</integer>
+
 </resources>
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
index 15f22c0..f7fe39e 100644
--- a/services/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -16,8 +16,6 @@
 
 package com.android.server;
 
-import com.android.internal.telephony.TelephonyIntents;
-
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -28,7 +26,6 @@
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.SntpClient;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -36,12 +33,10 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
-import android.util.Slog;
+import android.util.NtpTrustedTime;
+import android.util.TrustedTime;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
+import com.android.internal.telephony.TelephonyIntents;
 
 /**
  * Monitors the network time and updates the system time if it is out of sync
@@ -68,14 +63,11 @@
     private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
     /** Number of times to try again */
     private static final int TRY_AGAIN_TIMES_MAX = 3;
-    /** How long to wait for the NTP server to respond. */
-    private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000;
     /** If the time difference is greater than this threshold, then update the time. */
     private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
 
     private static final String ACTION_POLL =
             "com.android.server.NetworkTimeUpdateService.action.POLL";
-    private static final String PROPERTIES_FILE = "/etc/gps.conf";
     private static int POLL_REQUEST = 0;
 
     private static final long NOT_SET = -1;
@@ -84,14 +76,14 @@
     private long mNitzZoneSetTime = NOT_SET;
 
     private Context mContext;
+    private TrustedTime mTime;
+
     // NTP lookup is done on this thread and handler
     private Handler mHandler;
     private HandlerThread mThread;
     private AlarmManager mAlarmManager;
     private PendingIntent mPendingPollIntent;
     private SettingsObserver mSettingsObserver;
-    // Address of the NTP server
-    private String mNtpServer;
     // The last time that we successfully fetched the NTP time.
     private long mLastNtpFetchTime = NOT_SET;
     // Keeps track of how many quick attempts were made to fetch NTP time.
@@ -101,6 +93,7 @@
 
     public NetworkTimeUpdateService(Context context) {
         mContext = context;
+        mTime = NtpTrustedTime.getInstance(context);
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         Intent pollIntent = new Intent(ACTION_POLL, null);
         mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
@@ -108,12 +101,6 @@
 
     /** Initialize the receivers and initiate the first NTP request */
     public void systemReady() {
-        mNtpServer = getNtpServerAddress();
-        if (mNtpServer == null) {
-            Slog.e(TAG, "NTP server address not found, not syncing to NTP time");
-            return;
-        }
-
         registerForTelephonyIntents();
         registerForAlarms();
         registerForConnectivityIntents();
@@ -128,27 +115,6 @@
         mSettingsObserver.observe(mContext);
     }
 
-    private String getNtpServerAddress() {
-        String serverAddress = null;
-        FileInputStream stream = null;
-        try {
-            Properties properties = new Properties();
-            File file = new File(PROPERTIES_FILE);
-            stream = new FileInputStream(file);
-            properties.load(stream);
-            serverAddress = 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) {}
-            }
-        }
-        return serverAddress;
-    }
-
     private void registerForTelephonyIntents() {
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
@@ -189,9 +155,15 @@
         if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
                 || event == EVENT_AUTO_TIME_CHANGED) {
             if (DBG) Log.d(TAG, "Before Ntp fetch");
-            long ntp = getNtpTime();
-            if (DBG) Log.d(TAG, "Ntp = " + ntp);
-            if (ntp > 0) {
+
+            // force refresh NTP cache when outdated
+            if (mTime.getCacheAge() >= POLLING_INTERVAL_MS) {
+                mTime.forceRefresh();
+            }
+
+            // only update when NTP time is fresh
+            if (mTime.getCacheAge() < POLLING_INTERVAL_MS) {
+                final long ntp = mTime.currentTimeMillis();
                 mTryAgainCounter = 0;
                 mLastNtpFetchTime = SystemClock.elapsedRealtime();
                 if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) {
@@ -232,15 +204,6 @@
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
     }
 
-    private long getNtpTime() {
-        SntpClient client = new SntpClient();
-        if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) {
-            return client.getNtpTime();
-        } else {
-            return 0;
-        }
-    }
-
     /**
      * Checks if the user prefers to automatically set the time.
      */
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index d81dfdb..24d4dd3 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -16,9 +16,6 @@
 
 package com.android.server;
 
-import com.android.internal.R;
-import com.android.internal.telephony.TelephonyProperties;
-
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -54,6 +51,9 @@
 import android.util.Slog;
 import android.util.TrustedTime;
 
+import com.android.internal.R;
+import com.android.internal.telephony.TelephonyProperties;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -63,7 +63,6 @@
 import java.io.PrintWriter;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
-import java.util.Properties;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -87,7 +86,6 @@
     private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
 
     private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000;
-    private static final long MAX_NTP_FETCH_WAIT = 20 * 1000;
 
     private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE;
 
@@ -127,8 +125,6 @@
     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 Intent mPollStickyBroadcast;
 
     private TrustedTime mTime;
@@ -139,8 +135,7 @@
     }
 
     public ThrottleService(Context context) {
-        // TODO: move to using cached NtpTrustedTime
-        this(context, getNetworkManagementService(), new NtpTrustedTime(),
+        this(context, getNetworkManagementService(), NtpTrustedTime.getInstance(context),
                 context.getResources().getString(R.string.config_datause_iface));
     }
 
@@ -341,26 +336,6 @@
                 }
             }, new IntentFilter(ACTION_RESET));
 
-        FileInputStream stream = null;
-        try {
-            Properties properties = new Properties();
-            File file = new File(PROPERTIES_FILE);
-            stream = new FileInputStream(file);
-            properties.load(stream);
-            final String ntpServer = properties.getProperty("NTP_SERVER", null);
-            if (mTime instanceof NtpTrustedTime) {
-                ((NtpTrustedTime) mTime).setNtpServer(ntpServer, MAX_NTP_FETCH_WAIT);
-            }
-        } catch (IOException e) {
-            Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
-        } finally {
-            if (stream != null) {
-                try {
-                    stream.close();
-                } catch (Exception e) {}
-            }
-        }
-
         // use a new thread as we don't want to stall the system for file writes
         mThread = new HandlerThread(TAG);
         mThread.start();
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 4fa3bda..c813d37 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -32,7 +32,6 @@
 import android.location.LocationProvider;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.SntpClient;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -51,6 +50,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
 import android.util.Log;
+import android.util.NtpTrustedTime;
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IBatteryStats;
@@ -61,7 +61,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.StringBufferInputStream;
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Map.Entry;
@@ -235,13 +235,13 @@
 
     // properties loaded from PROPERTIES_FILE
     private Properties mProperties;
-    private String mNtpServer;
     private String mSuplServerHost;
     private int mSuplServerPort;
     private String mC2KServerHost;
     private int mC2KServerPort;
 
     private final Context mContext;
+    private final NtpTrustedTime mNtpTime;
     private final ILocationManager mLocationManager;
     private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
     private Bundle mLocationExtras = new Bundle();
@@ -286,10 +286,6 @@
     // current setting - 5 minutes
     private static final long RETRY_INTERVAL = 5*60*1000;
 
-    // to avoid injecting bad NTP time, we reject any time fixes that differ from system time
-    // by more than 5 minutes.
-    private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000;
-
     private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
         public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
             if (listener == null) {
@@ -378,6 +374,7 @@
 
     public GpsLocationProvider(Context context, ILocationManager locationManager) {
         mContext = context;
+        mNtpTime = NtpTrustedTime.getInstance(context);
         mLocationManager = locationManager;
         mNIHandler = new GpsNetInitiatedHandler(context);
 
@@ -418,7 +415,6 @@
             FileInputStream stream = new FileInputStream(file);
             mProperties.load(stream);
             stream.close();
-            mNtpServer = mProperties.getProperty("NTP_SERVER", null);
 
             mSuplServerHost = mProperties.getProperty("SUPL_HOST");
             String portString = mProperties.getProperty("SUPL_PORT");
@@ -530,13 +526,18 @@
         }
         mInjectNtpTimePending = false;
 
-        SntpClient client = new SntpClient();
         long delay;
 
-        if (client.requestTime(mNtpServer, 10000)) {
-            long time = client.getNtpTime();
-            long timeReference = client.getNtpTimeReference();
-            int certainty = (int)(client.getRoundTripTime()/2);
+        // force refresh NTP cache when outdated
+        if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+            mNtpTime.forceRefresh();
+        }
+
+        // only update when NTP time is fresh
+        if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
+            long time = mNtpTime.getCachedNtpTime();
+            long timeReference = mNtpTime.getCachedNtpTimeReference();
+            long certainty = mNtpTime.getCacheCertainty();
             long now = System.currentTimeMillis();
 
             Log.d(TAG, "NTP server returned: "
@@ -545,7 +546,7 @@
                     + " certainty: " + certainty
                     + " system time offset: " + (time - now));
 
-            native_inject_time(time, timeReference, certainty);
+            native_inject_time(time, timeReference, (int) certainty);
             delay = NTP_INTERVAL;
         } else {
             if (DEBUG) Log.d(TAG, "requestTime failed");
@@ -1395,7 +1396,7 @@
         Properties extraProp = new Properties();
 
         try {
-            extraProp.load(new StringBufferInputStream(extras));
+            extraProp.load(new StringReader(extras));
         }
         catch (IOException e)
         {
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 2a17cbe..d23d0f4 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -204,9 +204,8 @@
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
             IPowerManager powerManager, INetworkStatsService networkStats,
             INetworkManagementService networkManagement) {
-        // TODO: move to using cached NtpTrustedTime
         this(context, activityManager, powerManager, networkStats, networkManagement,
-                new NtpTrustedTime(), getSystemDir());
+                NtpTrustedTime.getInstance(context), getSystemDir());
     }
 
     private static File getSystemDir() {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index b4bd176..b6834f6 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -27,7 +27,6 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_REMOVED;
-import static android.provider.Settings.Secure.NETSTATS_ENABLED;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -71,7 +70,6 @@
 import android.util.TrustedTime;
 
 import com.android.internal.os.AtomicFile;
-import com.android.server.NativeDaemonConnectorException;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
 
@@ -175,9 +173,8 @@
 
     public NetworkStatsService(
             Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
-        // TODO: move to using cached NtpTrustedTime
-        this(context, networkManager, alarmManager, new NtpTrustedTime(), getSystemDir(),
-                new DefaultNetworkStatsSettings(context));
+        this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
+                getSystemDir(), new DefaultNetworkStatsSettings(context));
     }
 
     private static File getSystemDir() {