1/*
2 * Copyright (C) 2011 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 android.util;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.res.Resources;
22import android.net.ConnectivityManager;
23import android.net.NetworkInfo;
24import android.net.SntpClient;
25import android.os.SystemClock;
26import android.provider.Settings;
27import android.text.TextUtils;
28
29/**
30 * {@link TrustedTime} that connects with a remote NTP server as its trusted
31 * time source.
32 *
33 * @hide
34 */
35public class NtpTrustedTime implements TrustedTime {
36    private static final String TAG = "NtpTrustedTime";
37    private static final boolean LOGD = false;
38
39    private static NtpTrustedTime sSingleton;
40    private static Context sContext;
41
42    private final String mServer;
43    private final long mTimeout;
44
45    private ConnectivityManager mCM;
46
47    private boolean mHasCache;
48    private long mCachedNtpTime;
49    private long mCachedNtpElapsedRealtime;
50    private long mCachedNtpCertainty;
51
52    private NtpTrustedTime(String server, long timeout) {
53        if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
54        mServer = server;
55        mTimeout = timeout;
56    }
57
58    public static synchronized NtpTrustedTime getInstance(Context context) {
59        if (sSingleton == null) {
60            final Resources res = context.getResources();
61            final ContentResolver resolver = context.getContentResolver();
62
63            final String defaultServer = res.getString(
64                    com.android.internal.R.string.config_ntpServer);
65            final long defaultTimeout = res.getInteger(
66                    com.android.internal.R.integer.config_ntpTimeout);
67
68            final String secureServer = Settings.Global.getString(
69                    resolver, Settings.Global.NTP_SERVER);
70            final long timeout = Settings.Global.getLong(
71                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
72
73            final String server = secureServer != null ? secureServer : defaultServer;
74            sSingleton = new NtpTrustedTime(server, timeout);
75            sContext = context;
76        }
77
78        return sSingleton;
79    }
80
81    @Override
82    public boolean forceRefresh() {
83        if (TextUtils.isEmpty(mServer)) {
84            // missing server, so no trusted time available
85            return false;
86        }
87
88        // We can't do this at initialization time: ConnectivityService might not be running yet.
89        synchronized (this) {
90            if (mCM == null) {
91                mCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
92            }
93        }
94
95        final NetworkInfo ni = mCM == null ? null : mCM.getActiveNetworkInfo();
96        if (ni == null || !ni.isConnected()) {
97            if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
98            return false;
99        }
100
101
102        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
103        final SntpClient client = new SntpClient();
104        if (client.requestTime(mServer, (int) mTimeout)) {
105            mHasCache = true;
106            mCachedNtpTime = client.getNtpTime();
107            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
108            mCachedNtpCertainty = client.getRoundTripTime() / 2;
109            return true;
110        } else {
111            return false;
112        }
113    }
114
115    @Override
116    public boolean hasCache() {
117        return mHasCache;
118    }
119
120    @Override
121    public long getCacheAge() {
122        if (mHasCache) {
123            return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
124        } else {
125            return Long.MAX_VALUE;
126        }
127    }
128
129    @Override
130    public long getCacheCertainty() {
131        if (mHasCache) {
132            return mCachedNtpCertainty;
133        } else {
134            return Long.MAX_VALUE;
135        }
136    }
137
138    @Override
139    public long currentTimeMillis() {
140        if (!mHasCache) {
141            throw new IllegalStateException("Missing authoritative time source");
142        }
143        if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
144
145        // current time is age after the last ntp cache; callers who
146        // want fresh values will hit makeAuthoritative() first.
147        return mCachedNtpTime + getCacheAge();
148    }
149
150    public long getCachedNtpTime() {
151        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
152        return mCachedNtpTime;
153    }
154
155    public long getCachedNtpTimeReference() {
156        return mCachedNtpElapsedRealtime;
157    }
158}
159