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