/* * 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 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.ConnectivityManager; import android.net.NetworkInfo; 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.NtpTrustedTime; import android.util.TrustedTime; import com.android.internal.telephony.TelephonyIntents; /** * 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. *

* If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't * available. *

*/ 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; private static final int EVENT_WIFI_CONNECTED = 3; /** 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; /** 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 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; 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; // 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; 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); } /** Initialize the receivers and initiate the first NTP request */ public void systemReady() { registerForTelephonyIntents(); registerForAlarms(); registerForConnectivityIntents(); 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 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 registerForConnectivityIntents() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mConnectivityReceiver, intentFilter); } 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"); // 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) { // 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); } /** * 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(); } } }; /** Receiver for ConnectivityManager events */ private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { // There is connectivity NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra( ConnectivityManager.EXTRA_NETWORK_INFO); if (netInfo != null) { // Verify that it's a WIFI connection if (netInfo.getState() == NetworkInfo.State.CONNECTED && netInfo.getType() == ConnectivityManager.TYPE_WIFI ) { mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget(); } } } } }; /** 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: case EVENT_WIFI_CONNECTED: 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(); } } }