There are two ways to update the time in Android: NITZ and NTP.

NITZ: Network Identity and Time Zone (NITZ) is a mechanism for providing mobile devices with local date and Time, Time Zone, daylight saving Time offset, and Network provider Identity information over a wireless Network. This is typically used by mobile phones to automatically update system Time. NITZ technology has been an optional part of the official standard since GSM Phase 2 Release 96.

NTP: Network time protocol. English name: Network Time Protocol (NTP) is a Protocol used to synchronize computer Time. It enables a computer to synchronize its server or clock source (such as quartz clock, GPS, etc.). It provides high precision Time correction (the difference between the standard and the LAN is less than 1 millisecond, WAN tens of milliseconds). And it can be used to prevent malicious protocol attacks by means of encryption confirmation. NTP is designed to provide accurate and robust time services in an out-of-order Internet environment.

NTP

The Android system uses NTP to automatically update the system time using two trigger mechanisms:

  • Listen for the database field AUTO_TIME. When this field changes, a time synchronization is triggered immediately
  • The network connection changes. When the network is connected, a time check and synchronization is triggered
  • A timed update mechanism that triggers a time check and synchronization when a predetermined time is up

The Android system uses NTP to update the system time in NetworkTimeUpdateService. First, take a look at the initialization process of the service.

NetworkTimeUpdateService initialization

This is a others service, that is, an optional service. This service runs in the system_server process and is started in the SystemServer process.

frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices(a) {
    final Context context = mSystemContext;
    NetworkTimeUpdateService networkTimeUpdater = null;
    try {
        // Expand 1: instantiate, create a service, and add it as a system service
        networkTimeUpdater = new NetworkTimeUpdateService(context);
        ServiceManager.addService("network_time_update_service", networkTimeUpdater);
    } catch (Throwable e) {
        reportWtf("starting NetworkTimeUpdate service", e);
    }
    final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
    try {
        // Expand 2: start the service
        if(networkTimeUpdaterF ! =null) 
            networkTimeUpdaterF.systemRunning();
    } catch (Throwable e) {
        reportWtf("Notifying NetworkTimeService running", e); }}Copy the code

Expansion 1: Instantiate and create the service

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

public NetworkTimeUpdateService(Context context) {
    mContext = context;
    // Create an NTP instance. This is the class that uses the NTP protocol to obtain time
    mTime = NtpTrustedTime.getInstance(context);
    // Get the AlarmManager instance
    mAlarmManager = mContext.getSystemService(AlarmManager.class);
    // Get an instance of the network connection management class
    mCM = mContext.getSystemService(ConnectivityManager.class);

    Intent pollIntent = new Intent(ACTION_POLL, null);
    mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);

    // Obtain NTP time parameters from the system configuration file config.ini
    // The default value is 86400000ms, that is, 1 day
    mPollingIntervalMs = mContext.getResources().getInteger(
            com.android.internal.R.integer.config_ntpPollingInterval);
    // Due to network reasons, the retry interval after time synchronization fails. The default value is 60000ms, that is, 60s
    mPollingIntervalShorterMs = mContext.getResources().getInteger(
            com.android.internal.R.integer.config_ntpPollingIntervalShorter);
    // Retry times. The default is 3
    mTryAgainTimesMax = mContext.getResources().getInteger(
            com.android.internal.R.integer.config_ntpRetry);
    // The time error is 5000ms by default. When the time error exceeds 5s, the system time is updated
    mTimeErrorThresholdMs = mContext.getResources().getInteger(
            com.android.internal.R.integer.config_ntpThreshold);

    mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
            PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
Copy the code

Expansion 2: Start the service network_time_update_service

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

public void systemRunning(a) {
	// Receive the broadcast from NITZ after the system time update
    registerForTelephonyIntents();

    // Register an Alarm to periodically update the system time
    registerForAlarms();

    HandlerThread thread = new HandlerThread(TAG);
    thread.start();
    mHandler = new MyHandler(thread.getLooper());

    // Register the network callback, which will be called when the network changes
    mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
    mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);

    // Create ContentObserver and listen for changes to the AUTO_TIME database field
    mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
    mSettingsObserver.observe(mContext);
}
Copy the code

Listen for AUTO_TIME to update the system time

Next, look at how SettingsObserver works. This requires knowledge of ContentObserver. SettingsObserver inherits ContentObserver and passes on database fields that need to be listened to,

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

/** 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.Global.getUriFor(Settings.Global.AUTO_TIME),
                false.this);
    }

    @Override
    public void onChange(boolean selfChange) { mHandler.obtainMessage(mMsg).sendToTarget(); }}Copy the code

How ContentObserver works is not explained here; there will be a special article on ContentObserver later. When settings.global.auto_time changes, the onChange() method is called back by sending the message EVENT_AUTO_TIME_CHANGED. The place to receive the message and update the time is last unfolded.

Monitor network connection changes and update system time

private class NetworkTimeUpdateCallback extends NetworkCallback {
    @Override
    public void onAvailable(Network network) {
        Log.d(TAG, String.format("New default network %s; checking time.", network));
        mDefaultNetwork = network;
        // Running on mHandler so invoke directly.
        onPollNetworkTime(EVENT_NETWORK_CHANGED);
    }

    @Override
    public void onLost(Network network) {
        if (network.equals(mDefaultNetwork)) mDefaultNetwork = null; }}Copy the code

When the network connection is established, the onAvailable() method is called to update the system time by calling onPollNetworkTime().

Update the system time mechanism periodically

private void registerForAlarms(a) {
    mContext.registerReceiver(
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) { mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); }},new IntentFilter(ACTION_POLL));
}
Copy the code

Receive broadcast ACTION_POLL and send message EVENT_POLL_NETWORK_TIME to update the system time.

Updating the System Time

/** 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:  // Previously, the AUTO_TIME database field changed
            case EVENT_POLL_NETWORK_TIME:  // It is time for polling
            case EVENT_NETWORK_CHANGED:    // A new network connection is established
                onPollNetworkTime(msg.what);
                break; }}}private void onPollNetworkTime(int event) {
    // If Automatic time is not set, don't bother. Similarly, if we don't
    // have any default network, don't bother.
    if (mDefaultNetwork == null) return;
    mWakeLock.acquire();
    try {
        onPollNetworkTimeUnderWakeLock(event);
    } finally{ mWakeLock.release(); }}private void onPollNetworkTimeUnderWakeLock(int event) {
    // Force an NTP fix when outdated
    If the time is greater than or equal to 1 day since the last update, the system obtains the time from the NTP server again
    if (mTime.getCacheAge() >= mPollingIntervalMs) {
        if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
        mTime.forceRefresh();
    }
    // If the NTP server successfully obtains the time, the timer is reset and the system time is updated
    if (mTime.getCacheAge() < mPollingIntervalMs) {
        // Obtained fresh fix; schedule next normal update
        resetAlarm(mPollingIntervalMs);
        if (isAutomaticTimeRequested()) {
            updateSystemClock(event);
        }
    // Otherwise, enter retry
    } else {
        // No fresh fix; schedule retry
        mTryAgainCounter++;
        // If the retry times do not run out, use 60s to reset the timer
        if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
            resetAlarm(mPollingIntervalShorterMs);
        // Otherwise, use 1 day to reset the timer
        } else {
            // Try much later
            mTryAgainCounter = 0; resetAlarm(mPollingIntervalMs); }}}private void updateSystemClock(int event) {
	// If the automatic update time is enabled by the user, force update is enabled
    final boolean forceUpdate = (event == EVENT_AUTO_TIME_CHANGED);
    if(! forceUpdate) {// If the system time has been updated through NITZ within a day, it will not be updated
        if (getNitzAge() < mPollingIntervalMs) {
            if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ");
            return;
        }

        final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis());
        // If the difference between the NTP server time and the system time is less than 5s, the system time will not be updated
        if (skew < mTimeErrorThresholdMs) {
            if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew");
            return; }}// AlarmManagerService is used to update the system time
    SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis());
}
Copy the code

NtpTrustedTime

To take a closer look at the NTP implementation code for Android, check out the source file below.

frameworks\base\core\java\android\util\TrustedTime.java
frameworks\base\core\java\android\util\NtpTrustedTime.java
frameworks\base\core\java\android\net\SntpClient.java
Copy the code

NITZ

The NITZ code is found in the telephony module, where:

frameworks/opt/telephony

Extended learning

This article mainly shares the principle and implementation process of Android system automatically updating system time through NTP. There are some techniques that you can expand on:

  • ContentObserver
  • PendingIntent
  • Alarm Manager/Service