1/*
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 android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.database.ContentObserver;
27import android.net.ConnectivityManager;
28import android.net.NetworkInfo;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.Looper;
32import android.os.Message;
33import android.os.SystemClock;
34import android.provider.Settings;
35import android.util.Log;
36import android.util.NtpTrustedTime;
37import android.util.TrustedTime;
38
39import com.android.internal.telephony.TelephonyIntents;
40
41/**
42 * Monitors the network time and updates the system time if it is out of sync
43 * and there hasn't been any NITZ update from the carrier recently.
44 * If looking up the network time fails for some reason, it tries a few times with a short
45 * interval and then resets to checking on longer intervals.
46 * <p>
47 * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
48 * available.
49 * </p>
50 */
51public class NetworkTimeUpdateService {
52
53    private static final String TAG = "NetworkTimeUpdateService";
54    private static final boolean DBG = false;
55
56    private static final int EVENT_AUTO_TIME_CHANGED = 1;
57    private static final int EVENT_POLL_NETWORK_TIME = 2;
58    private static final int EVENT_NETWORK_CONNECTED = 3;
59
60    private static final String ACTION_POLL =
61            "com.android.server.NetworkTimeUpdateService.action.POLL";
62    private static int POLL_REQUEST = 0;
63
64    private static final long NOT_SET = -1;
65    private long mNitzTimeSetTime = NOT_SET;
66    // TODO: Have a way to look up the timezone we are in
67    private long mNitzZoneSetTime = NOT_SET;
68
69    private Context mContext;
70    private TrustedTime mTime;
71
72    // NTP lookup is done on this thread and handler
73    private Handler mHandler;
74    private HandlerThread mThread;
75    private AlarmManager mAlarmManager;
76    private PendingIntent mPendingPollIntent;
77    private SettingsObserver mSettingsObserver;
78    // The last time that we successfully fetched the NTP time.
79    private long mLastNtpFetchTime = NOT_SET;
80
81    // Normal polling frequency
82    private final long mPollingIntervalMs;
83    // Try-again polling interval, in case the network request failed
84    private final long mPollingIntervalShorterMs;
85    // Number of times to try again
86    private final int mTryAgainTimesMax;
87    // If the time difference is greater than this threshold, then update the time.
88    private final int mTimeErrorThresholdMs;
89    // Keeps track of how many quick attempts were made to fetch NTP time.
90    // During bootup, the network may not have been up yet, or it's taking time for the
91    // connection to happen.
92    private int mTryAgainCounter;
93
94    public NetworkTimeUpdateService(Context context) {
95        mContext = context;
96        mTime = NtpTrustedTime.getInstance(context);
97        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
98        Intent pollIntent = new Intent(ACTION_POLL, null);
99        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
100
101        mPollingIntervalMs = mContext.getResources().getInteger(
102                com.android.internal.R.integer.config_ntpPollingInterval);
103        mPollingIntervalShorterMs = mContext.getResources().getInteger(
104                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
105        mTryAgainTimesMax = mContext.getResources().getInteger(
106                com.android.internal.R.integer.config_ntpRetry);
107        mTimeErrorThresholdMs = mContext.getResources().getInteger(
108                com.android.internal.R.integer.config_ntpThreshold);
109    }
110
111    /** Initialize the receivers and initiate the first NTP request */
112    public void systemReady() {
113        registerForTelephonyIntents();
114        registerForAlarms();
115        registerForConnectivityIntents();
116
117        mThread = new HandlerThread(TAG);
118        mThread.start();
119        mHandler = new MyHandler(mThread.getLooper());
120        // Check the network time on the new thread
121        mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
122
123        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
124        mSettingsObserver.observe(mContext);
125    }
126
127    private void registerForTelephonyIntents() {
128        IntentFilter intentFilter = new IntentFilter();
129        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
130        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
131        mContext.registerReceiver(mNitzReceiver, intentFilter);
132    }
133
134    private void registerForAlarms() {
135        mContext.registerReceiver(
136            new BroadcastReceiver() {
137                @Override
138                public void onReceive(Context context, Intent intent) {
139                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
140                }
141            }, new IntentFilter(ACTION_POLL));
142    }
143
144    private void registerForConnectivityIntents() {
145        IntentFilter intentFilter = new IntentFilter();
146        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
147        mContext.registerReceiver(mConnectivityReceiver, intentFilter);
148    }
149
150    private void onPollNetworkTime(int event) {
151        // If Automatic time is not set, don't bother.
152        if (!isAutomaticTimeRequested()) return;
153
154        final long refTime = SystemClock.elapsedRealtime();
155        // If NITZ time was received less than mPollingIntervalMs time ago,
156        // no need to sync to NTP.
157        if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
158            resetAlarm(mPollingIntervalMs);
159            return;
160        }
161        final long currentTime = System.currentTimeMillis();
162        if (DBG) Log.d(TAG, "System time = " + currentTime);
163        // Get the NTP time
164        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
165                || event == EVENT_AUTO_TIME_CHANGED) {
166            if (DBG) Log.d(TAG, "Before Ntp fetch");
167
168            // force refresh NTP cache when outdated
169            if (mTime.getCacheAge() >= mPollingIntervalMs) {
170                mTime.forceRefresh();
171            }
172
173            // only update when NTP time is fresh
174            if (mTime.getCacheAge() < mPollingIntervalMs) {
175                final long ntp = mTime.currentTimeMillis();
176                mTryAgainCounter = 0;
177                // If the clock is more than N seconds off or this is the first time it's been
178                // fetched since boot, set the current time.
179                if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
180                        || mLastNtpFetchTime == NOT_SET) {
181                    // Set the system time
182                    if (DBG && mLastNtpFetchTime == NOT_SET
183                            && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
184                        Log.d(TAG, "For initial setup, rtc = " + currentTime);
185                    }
186                    if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
187                    // Make sure we don't overflow, since it's going to be converted to an int
188                    if (ntp / 1000 < Integer.MAX_VALUE) {
189                        SystemClock.setCurrentTimeMillis(ntp);
190                    }
191                } else {
192                    if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
193                }
194                mLastNtpFetchTime = SystemClock.elapsedRealtime();
195            } else {
196                // Try again shortly
197                mTryAgainCounter++;
198                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
199                    resetAlarm(mPollingIntervalShorterMs);
200                } else {
201                    // Try much later
202                    mTryAgainCounter = 0;
203                    resetAlarm(mPollingIntervalMs);
204                }
205                return;
206            }
207        }
208        resetAlarm(mPollingIntervalMs);
209    }
210
211    /**
212     * Cancel old alarm and starts a new one for the specified interval.
213     *
214     * @param interval when to trigger the alarm, starting from now.
215     */
216    private void resetAlarm(long interval) {
217        mAlarmManager.cancel(mPendingPollIntent);
218        long now = SystemClock.elapsedRealtime();
219        long next = now + interval;
220        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
221    }
222
223    /**
224     * Checks if the user prefers to automatically set the time.
225     */
226    private boolean isAutomaticTimeRequested() {
227        return Settings.Global.getInt(
228                mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0;
229    }
230
231    /** Receiver for Nitz time events */
232    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
233
234        @Override
235        public void onReceive(Context context, Intent intent) {
236            String action = intent.getAction();
237            if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
238                mNitzTimeSetTime = SystemClock.elapsedRealtime();
239            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
240                mNitzZoneSetTime = SystemClock.elapsedRealtime();
241            }
242        }
243    };
244
245    /** Receiver for ConnectivityManager events */
246    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
247
248        @Override
249        public void onReceive(Context context, Intent intent) {
250            String action = intent.getAction();
251            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
252                // There is connectivity
253                final ConnectivityManager connManager = (ConnectivityManager) context
254                        .getSystemService(Context.CONNECTIVITY_SERVICE);
255                final NetworkInfo netInfo = connManager.getActiveNetworkInfo();
256                if (netInfo != null) {
257                    // Verify that it's a WIFI connection
258                    if (netInfo.getState() == NetworkInfo.State.CONNECTED &&
259                            (netInfo.getType() == ConnectivityManager.TYPE_WIFI ||
260                                netInfo.getType() == ConnectivityManager.TYPE_ETHERNET) ) {
261                        mHandler.obtainMessage(EVENT_NETWORK_CONNECTED).sendToTarget();
262                    }
263                }
264            }
265        }
266    };
267
268    /** Handler to do the network accesses on */
269    private class MyHandler extends Handler {
270
271        public MyHandler(Looper l) {
272            super(l);
273        }
274
275        @Override
276        public void handleMessage(Message msg) {
277            switch (msg.what) {
278                case EVENT_AUTO_TIME_CHANGED:
279                case EVENT_POLL_NETWORK_TIME:
280                case EVENT_NETWORK_CONNECTED:
281                    onPollNetworkTime(msg.what);
282                    break;
283            }
284        }
285    }
286
287    /** Observer to watch for changes to the AUTO_TIME setting */
288    private static class SettingsObserver extends ContentObserver {
289
290        private int mMsg;
291        private Handler mHandler;
292
293        SettingsObserver(Handler handler, int msg) {
294            super(handler);
295            mHandler = handler;
296            mMsg = msg;
297        }
298
299        void observe(Context context) {
300            ContentResolver resolver = context.getContentResolver();
301            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
302                    false, this);
303        }
304
305        @Override
306        public void onChange(boolean selfChange) {
307            mHandler.obtainMessage(mMsg).sendToTarget();
308        }
309    }
310}
311