diff options
Diffstat (limited to 'services/java/com/android/server/NetworkTimeUpdateService.java')
-rw-r--r-- | services/java/com/android/server/NetworkTimeUpdateService.java | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java new file mode 100644 index 000000000000..52f84ebf57b8 --- /dev/null +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import com.android.internal.telephony.TelephonyIntents; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.SntpClient; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Monitors the network time and updates the system time if it is out of sync + * and there hasn't been any NITZ update from the carrier recently. + * If looking up the network time fails for some reason, it tries a few times with a short + * interval and then resets to checking on longer intervals. + * <p> + * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't + * available. + * </p> + */ +public class NetworkTimeUpdateService { + + private static final String TAG = "NetworkTimeUpdateService"; + private static final boolean DBG = false; + + private static final int EVENT_AUTO_TIME_CHANGED = 1; + private static final int EVENT_POLL_NETWORK_TIME = 2; + + /** Normal polling frequency */ + private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs + /** Try-again polling interval, in case the network request failed */ + 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; + private long mNitzTimeSetTime = NOT_SET; + // TODO: Have a way to look up the timezone we are in + private long mNitzZoneSetTime = NOT_SET; + + private Context mContext; + // 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. + // During bootup, the network may not have been up yet, or it's taking time for the + // connection to happen. + private int mTryAgainCounter; + + public NetworkTimeUpdateService(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + Intent pollIntent = new Intent(ACTION_POLL, null); + mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + } + + /** 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(); + + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + // Check the network time on the new thread + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); + 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); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); + mContext.registerReceiver(mNitzReceiver, intentFilter); + } + + private void registerForAlarms() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + } + }, new IntentFilter(ACTION_POLL)); + } + + private void onPollNetworkTime(int event) { + // If Automatic time is not set, don't bother. + if (!isAutomaticTimeRequested()) return; + + final long refTime = SystemClock.elapsedRealtime(); + // If NITZ time was received less than POLLING_INTERVAL_MS time ago, + // no need to sync to NTP. + if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) { + resetAlarm(POLLING_INTERVAL_MS); + return; + } + final long currentTime = System.currentTimeMillis(); + if (DBG) Log.d(TAG, "System time = " + currentTime); + // Get the NTP time + 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) { + mTryAgainCounter = 0; + mLastNtpFetchTime = SystemClock.elapsedRealtime(); + if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) { + // Set the system time + if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp); + // Make sure we don't overflow, since it's going to be converted to an int + if (ntp / 1000 < Integer.MAX_VALUE) { + SystemClock.setCurrentTimeMillis(ntp); + } + } else { + if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp); + } + } else { + // Try again shortly + mTryAgainCounter++; + if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) { + resetAlarm(POLLING_INTERVAL_SHORTER_MS); + } else { + // Try much later + mTryAgainCounter = 0; + resetAlarm(POLLING_INTERVAL_MS); + } + return; + } + } + resetAlarm(POLLING_INTERVAL_MS); + } + + /** + * Cancel old alarm and starts a new one for the specified interval. + * + * @param interval when to trigger the alarm, starting from now. + */ + private void resetAlarm(long interval) { + mAlarmManager.cancel(mPendingPollIntent); + long now = SystemClock.elapsedRealtime(); + long next = now + interval; + 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. + */ + private boolean isAutomaticTimeRequested() { + return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0) + != 0; + } + + /** Receiver for Nitz time events */ + private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { + mNitzTimeSetTime = SystemClock.elapsedRealtime(); + } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { + mNitzZoneSetTime = SystemClock.elapsedRealtime(); + } + } + }; + + /** Handler to do the network accesses on */ + private class MyHandler extends Handler { + + public MyHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_TIME_CHANGED: + case EVENT_POLL_NETWORK_TIME: + onPollNetworkTime(msg.what); + break; + } + } + } + + /** Observer to watch for changes to the AUTO_TIME setting */ + private static class SettingsObserver extends ContentObserver { + + private int mMsg; + private Handler mHandler; + + SettingsObserver(Handler handler, int msg) { + super(handler); + mHandler = handler; + mMsg = msg; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME), + false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } +} |