blob: 15f22c0b2ad5dba5b8796338674bd57213390192 [file] [log] [blame]
Amith Yamasani6734b9f2010-09-13 16:24:08 -07001/*
2 * Copyright (C) 2010 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 com.android.internal.telephony.TelephonyIntents;
20
21import android.app.AlarmManager;
22import android.app.PendingIntent;
23import android.content.BroadcastReceiver;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.database.ContentObserver;
Amith Yamasani8d394fa2011-03-01 12:41:04 -080029import android.net.ConnectivityManager;
30import android.net.NetworkInfo;
Amith Yamasani6734b9f2010-09-13 16:24:08 -070031import android.net.SntpClient;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.Looper;
35import android.os.Message;
36import android.os.SystemClock;
37import android.provider.Settings;
38import android.util.Log;
39import android.util.Slog;
40
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.IOException;
44import java.util.Properties;
45
46/**
47 * Monitors the network time and updates the system time if it is out of sync
48 * and there hasn't been any NITZ update from the carrier recently.
49 * If looking up the network time fails for some reason, it tries a few times with a short
50 * interval and then resets to checking on longer intervals.
51 * <p>
52 * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
53 * available.
54 * </p>
55 */
56public class NetworkTimeUpdateService {
57
58 private static final String TAG = "NetworkTimeUpdateService";
59 private static final boolean DBG = false;
60
61 private static final int EVENT_AUTO_TIME_CHANGED = 1;
62 private static final int EVENT_POLL_NETWORK_TIME = 2;
Amith Yamasani8d394fa2011-03-01 12:41:04 -080063 private static final int EVENT_WIFI_CONNECTED = 3;
Amith Yamasani6734b9f2010-09-13 16:24:08 -070064
65 /** Normal polling frequency */
66 private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
67 /** Try-again polling interval, in case the network request failed */
68 private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
69 /** Number of times to try again */
70 private static final int TRY_AGAIN_TIMES_MAX = 3;
71 /** How long to wait for the NTP server to respond. */
72 private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000;
73 /** If the time difference is greater than this threshold, then update the time. */
74 private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
75
76 private static final String ACTION_POLL =
77 "com.android.server.NetworkTimeUpdateService.action.POLL";
78 private static final String PROPERTIES_FILE = "/etc/gps.conf";
79 private static int POLL_REQUEST = 0;
80
81 private static final long NOT_SET = -1;
82 private long mNitzTimeSetTime = NOT_SET;
83 // TODO: Have a way to look up the timezone we are in
84 private long mNitzZoneSetTime = NOT_SET;
85
86 private Context mContext;
87 // NTP lookup is done on this thread and handler
88 private Handler mHandler;
89 private HandlerThread mThread;
90 private AlarmManager mAlarmManager;
91 private PendingIntent mPendingPollIntent;
92 private SettingsObserver mSettingsObserver;
93 // Address of the NTP server
94 private String mNtpServer;
95 // The last time that we successfully fetched the NTP time.
96 private long mLastNtpFetchTime = NOT_SET;
97 // Keeps track of how many quick attempts were made to fetch NTP time.
98 // During bootup, the network may not have been up yet, or it's taking time for the
99 // connection to happen.
100 private int mTryAgainCounter;
101
102 public NetworkTimeUpdateService(Context context) {
103 mContext = context;
104 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
105 Intent pollIntent = new Intent(ACTION_POLL, null);
106 mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
107 }
108
109 /** Initialize the receivers and initiate the first NTP request */
110 public void systemReady() {
111 mNtpServer = getNtpServerAddress();
112 if (mNtpServer == null) {
113 Slog.e(TAG, "NTP server address not found, not syncing to NTP time");
114 return;
115 }
116
117 registerForTelephonyIntents();
118 registerForAlarms();
Amith Yamasani8d394fa2011-03-01 12:41:04 -0800119 registerForConnectivityIntents();
Amith Yamasani6734b9f2010-09-13 16:24:08 -0700120
121 mThread = new HandlerThread(TAG);
122 mThread.start();
123 mHandler = new MyHandler(mThread.getLooper());
124 // Check the network time on the new thread
125 mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
126
127 mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
128 mSettingsObserver.observe(mContext);
129 }
130
131 private String getNtpServerAddress() {
132 String serverAddress = null;
133 FileInputStream stream = null;
134 try {
135 Properties properties = new Properties();
136 File file = new File(PROPERTIES_FILE);
137 stream = new FileInputStream(file);
138 properties.load(stream);
139 serverAddress = properties.getProperty("NTP_SERVER", null);
140 } catch (IOException e) {
141 Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
142 } finally {
143 if (stream != null) {
144 try {
145 stream.close();
146 } catch (Exception e) {}
147 }
148 }
149 return serverAddress;
150 }
151
152 private void registerForTelephonyIntents() {
153 IntentFilter intentFilter = new IntentFilter();
154 intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
155 intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
156 mContext.registerReceiver(mNitzReceiver, intentFilter);
157 }
158
159 private void registerForAlarms() {
160 mContext.registerReceiver(
161 new BroadcastReceiver() {
162 @Override
163 public void onReceive(Context context, Intent intent) {
164 mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
165 }
166 }, new IntentFilter(ACTION_POLL));
167 }
168
Amith Yamasani8d394fa2011-03-01 12:41:04 -0800169 private void registerForConnectivityIntents() {
170 IntentFilter intentFilter = new IntentFilter();
171 intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
172 mContext.registerReceiver(mConnectivityReceiver, intentFilter);
173 }
174
Amith Yamasani6734b9f2010-09-13 16:24:08 -0700175 private void onPollNetworkTime(int event) {
176 // If Automatic time is not set, don't bother.
177 if (!isAutomaticTimeRequested()) return;
178
179 final long refTime = SystemClock.elapsedRealtime();
180 // If NITZ time was received less than POLLING_INTERVAL_MS time ago,
181 // no need to sync to NTP.
182 if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) {
183 resetAlarm(POLLING_INTERVAL_MS);
184 return;
185 }
186 final long currentTime = System.currentTimeMillis();
187 if (DBG) Log.d(TAG, "System time = " + currentTime);
188 // Get the NTP time
189 if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
190 || event == EVENT_AUTO_TIME_CHANGED) {
191 if (DBG) Log.d(TAG, "Before Ntp fetch");
192 long ntp = getNtpTime();
193 if (DBG) Log.d(TAG, "Ntp = " + ntp);
194 if (ntp > 0) {
195 mTryAgainCounter = 0;
196 mLastNtpFetchTime = SystemClock.elapsedRealtime();
197 if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) {
198 // Set the system time
199 if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
200 // Make sure we don't overflow, since it's going to be converted to an int
201 if (ntp / 1000 < Integer.MAX_VALUE) {
202 SystemClock.setCurrentTimeMillis(ntp);
203 }
204 } else {
205 if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
206 }
207 } else {
208 // Try again shortly
209 mTryAgainCounter++;
210 if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) {
211 resetAlarm(POLLING_INTERVAL_SHORTER_MS);
212 } else {
213 // Try much later
214 mTryAgainCounter = 0;
215 resetAlarm(POLLING_INTERVAL_MS);
216 }
217 return;
218 }
219 }
220 resetAlarm(POLLING_INTERVAL_MS);
221 }
222
223 /**
224 * Cancel old alarm and starts a new one for the specified interval.
225 *
226 * @param interval when to trigger the alarm, starting from now.
227 */
228 private void resetAlarm(long interval) {
229 mAlarmManager.cancel(mPendingPollIntent);
230 long now = SystemClock.elapsedRealtime();
231 long next = now + interval;
232 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
233 }
234
235 private long getNtpTime() {
236 SntpClient client = new SntpClient();
237 if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) {
238 return client.getNtpTime();
239 } else {
240 return 0;
241 }
242 }
243
244 /**
245 * Checks if the user prefers to automatically set the time.
246 */
247 private boolean isAutomaticTimeRequested() {
248 return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0)
249 != 0;
250 }
251
252 /** Receiver for Nitz time events */
253 private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
254
255 @Override
256 public void onReceive(Context context, Intent intent) {
257 String action = intent.getAction();
258 if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
259 mNitzTimeSetTime = SystemClock.elapsedRealtime();
260 } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
261 mNitzZoneSetTime = SystemClock.elapsedRealtime();
262 }
263 }
264 };
265
Amith Yamasani8d394fa2011-03-01 12:41:04 -0800266 /** Receiver for ConnectivityManager events */
267 private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
268
269 @Override
270 public void onReceive(Context context, Intent intent) {
271 String action = intent.getAction();
272 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
273 // There is connectivity
274 NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra(
275 ConnectivityManager.EXTRA_NETWORK_INFO);
276 if (netInfo != null) {
277 // Verify that it's a WIFI connection
278 if (netInfo.getState() == NetworkInfo.State.CONNECTED &&
279 netInfo.getType() == ConnectivityManager.TYPE_WIFI ) {
280 mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget();
281 }
282 }
283 }
284 }
285 };
286
Amith Yamasani6734b9f2010-09-13 16:24:08 -0700287 /** Handler to do the network accesses on */
288 private class MyHandler extends Handler {
289
290 public MyHandler(Looper l) {
291 super(l);
292 }
293
294 @Override
295 public void handleMessage(Message msg) {
296 switch (msg.what) {
297 case EVENT_AUTO_TIME_CHANGED:
298 case EVENT_POLL_NETWORK_TIME:
Amith Yamasani8d394fa2011-03-01 12:41:04 -0800299 case EVENT_WIFI_CONNECTED:
Amith Yamasani6734b9f2010-09-13 16:24:08 -0700300 onPollNetworkTime(msg.what);
301 break;
302 }
303 }
304 }
305
306 /** Observer to watch for changes to the AUTO_TIME setting */
307 private static class SettingsObserver extends ContentObserver {
308
309 private int mMsg;
310 private Handler mHandler;
311
312 SettingsObserver(Handler handler, int msg) {
313 super(handler);
314 mHandler = handler;
315 mMsg = msg;
316 }
317
318 void observe(Context context) {
319 ContentResolver resolver = context.getContentResolver();
320 resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME),
321 false, this);
322 }
323
324 @Override
325 public void onChange(boolean selfChange) {
326 mHandler.obtainMessage(mMsg).sendToTarget();
327 }
328 }
329}