1 /*
2  * Copyright 2017 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 
17 package com.android.internal.telephony;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.os.PowerManager;
22 import android.os.SystemProperties;
23 import android.provider.Settings;
24 import android.telephony.Rlog;
25 import android.telephony.TelephonyManager;
26 import android.text.TextUtils;
27 import android.util.LocalLog;
28 import android.util.TimeUtils;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
32 import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
33 import com.android.internal.telephony.metrics.TelephonyMetrics;
34 import com.android.internal.telephony.util.TimeStampedValue;
35 import com.android.internal.util.IndentingPrintWriter;
36 
37 import java.io.FileDescriptor;
38 import java.io.PrintWriter;
39 import java.util.TimeZone;
40 
41 /**
42  * {@hide}
43  */
44 // Non-final to allow mocking.
45 public class NitzStateMachine {
46 
47     /**
48      * A proxy over device state that allows things like system properties, system clock
49      * to be faked for tests.
50      */
51     // Non-final to allow mocking.
52     public static class DeviceState {
53         private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
54         private final int mNitzUpdateSpacing;
55 
56         private static final int NITZ_UPDATE_DIFF_DEFAULT = 2000;
57         private final int mNitzUpdateDiff;
58 
59         private final GsmCdmaPhone mPhone;
60         private final TelephonyManager mTelephonyManager;
61         private final ContentResolver mCr;
62 
DeviceState(GsmCdmaPhone phone)63         public DeviceState(GsmCdmaPhone phone) {
64             mPhone = phone;
65 
66             Context context = phone.getContext();
67             mTelephonyManager =
68                     (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
69             mCr = context.getContentResolver();
70             mNitzUpdateSpacing =
71                     SystemProperties.getInt("ro.nitz_update_spacing", NITZ_UPDATE_SPACING_DEFAULT);
72             mNitzUpdateDiff =
73                     SystemProperties.getInt("ro.nitz_update_diff", NITZ_UPDATE_DIFF_DEFAULT);
74         }
75 
76         /**
77          * If time between NITZ updates is less than {@link #getNitzUpdateSpacingMillis()} the
78          * update may be ignored.
79          */
getNitzUpdateSpacingMillis()80         public int getNitzUpdateSpacingMillis() {
81             return Settings.Global.getInt(mCr, Settings.Global.NITZ_UPDATE_SPACING,
82                     mNitzUpdateSpacing);
83         }
84 
85         /**
86          * If {@link #getNitzUpdateSpacingMillis()} hasn't been exceeded but update is >
87          * {@link #getNitzUpdateDiffMillis()} do the update
88          */
getNitzUpdateDiffMillis()89         public int getNitzUpdateDiffMillis() {
90             return Settings.Global.getInt(mCr, Settings.Global.NITZ_UPDATE_DIFF, mNitzUpdateDiff);
91         }
92 
93         /**
94          * Returns true if the {@code gsm.ignore-nitz} system property is set to "yes".
95          */
getIgnoreNitz()96         public boolean getIgnoreNitz() {
97             String ignoreNitz = SystemProperties.get("gsm.ignore-nitz");
98             return ignoreNitz != null && ignoreNitz.equals("yes");
99         }
100 
getNetworkCountryIsoForPhone()101         public String getNetworkCountryIsoForPhone() {
102             return mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
103         }
104     }
105 
106     private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
107     private static final boolean DBG = ServiceStateTracker.DBG;
108 
109     // Time detection state.
110 
111     /**
112      * The last NITZ-sourced time considered. If auto time detection was off at the time this may
113      * not have been used to set the device time, but it can be used if auto time detection is
114      * re-enabled.
115      */
116     private TimeStampedValue<Long> mSavedNitzTime;
117 
118     // Time Zone detection state.
119 
120     /**
121      * Sometimes we get the NITZ time before we know what country we are in. We keep the time zone
122      * information from the NITZ string in mLatestNitzSignal so we can fix the time zone once we
123      * know the country.
124      */
125     private boolean mNeedCountryCodeForNitz = false;
126 
127     private TimeStampedValue<NitzData> mLatestNitzSignal;
128     private boolean mGotCountryCode = false;
129     private String mSavedTimeZoneId;
130 
131     /**
132      * Boolean is {@code true} if {@link #handleNitzReceived(TimeStampedValue)} has been called and
133      * was able to determine a time zone (which may not ultimately have been used due to user
134      * settings). Cleared by {@link #handleNetworkAvailable()} and
135      * {@link #handleNetworkUnavailable()}. The flag can be used when historic NITZ data may no
136      * longer be valid. {@code true} indicates it's not reasonable to try to set the time zone using
137      * less reliable algorithms than NITZ-based detection such as by just using network country
138      * code.
139      */
140     private boolean mNitzTimeZoneDetectionSuccessful = false;
141 
142     // Miscellaneous dependencies and helpers not related to detection state.
143     private final LocalLog mTimeLog = new LocalLog(15);
144     private final LocalLog mTimeZoneLog = new LocalLog(15);
145     private final GsmCdmaPhone mPhone;
146     private final DeviceState mDeviceState;
147     private final TimeServiceHelper mTimeServiceHelper;
148     private final TimeZoneLookupHelper mTimeZoneLookupHelper;
149     /** Wake lock used while setting time of day. */
150     private final PowerManager.WakeLock mWakeLock;
151     private static final String WAKELOCK_TAG = "NitzStateMachine";
152 
NitzStateMachine(GsmCdmaPhone phone)153     public NitzStateMachine(GsmCdmaPhone phone) {
154         this(phone,
155                 new TimeServiceHelper(phone.getContext()),
156                 new DeviceState(phone),
157                 new TimeZoneLookupHelper());
158     }
159 
160     @VisibleForTesting
NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper, DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper)161     public NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
162             DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
163         mPhone = phone;
164 
165         Context context = phone.getContext();
166         PowerManager powerManager =
167                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
168         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
169 
170         mDeviceState = deviceState;
171         mTimeZoneLookupHelper = timeZoneLookupHelper;
172         mTimeServiceHelper = timeServiceHelper;
173         mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
174             @Override
175             public void onTimeDetectionChange(boolean enabled) {
176                 if (enabled) {
177                     handleAutoTimeEnabled();
178                 }
179             }
180 
181             @Override
182             public void onTimeZoneDetectionChange(boolean enabled) {
183                 if (enabled) {
184                     handleAutoTimeZoneEnabled();
185                 }
186             }
187         });
188     }
189 
190     /**
191      * Called when the network country is set on the Phone. Although set, the network country code
192      * may be invalid.
193      *
194      * @param countryChanged true when the country code is known to have changed, false if it
195      *     probably hasn't
196      */
handleNetworkCountryCodeSet(boolean countryChanged)197     public void handleNetworkCountryCodeSet(boolean countryChanged) {
198         mGotCountryCode = true;
199 
200         String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
201         if (!TextUtils.isEmpty(isoCountryCode)
202                 && !mNitzTimeZoneDetectionSuccessful
203                 && mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
204             updateTimeZoneByNetworkCountryCode(isoCountryCode);
205         }
206 
207         if (countryChanged || mNeedCountryCodeForNitz) {
208             // TimeZone.getDefault() returns a default zone (GMT) even when time zone have never
209             // been set which makes it difficult to tell if it's what the user / time zone detection
210             // has chosen. isTimeZoneSettingInitialized() tells us whether the time zone of the
211             // device has ever been explicit set by the user or code.
212             final boolean isTimeZoneSettingInitialized =
213                     mTimeServiceHelper.isTimeZoneSettingInitialized();
214             if (DBG) {
215                 Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
216                         + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
217                         + " mLatestNitzSignal=" + mLatestNitzSignal
218                         + " isoCountryCode=" + isoCountryCode);
219             }
220             String zoneId;
221             if (TextUtils.isEmpty(isoCountryCode) && mNeedCountryCodeForNitz) {
222                 // Country code not found.  This is likely a test network.
223                 // Get a TimeZone based only on the NITZ parameters (best guess).
224 
225                 // mNeedCountryCodeForNitz is only set to true when mLatestNitzSignal is set so
226                 // there's no need to check mLatestNitzSignal == null.
227                 OffsetResult lookupResult =
228                         mTimeZoneLookupHelper.lookupByNitz(mLatestNitzSignal.mValue);
229                 if (DBG) {
230                     Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: guessZoneIdByNitz() returned"
231                             + " lookupResult=" + lookupResult);
232                 }
233                 zoneId = lookupResult != null ? lookupResult.zoneId : null;
234             } else if (mLatestNitzSignal == null) {
235                 zoneId = null;
236                 if (DBG) {
237                     Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: No cached NITZ data available,"
238                             + " not setting zone");
239                 }
240             } else { // mLatestNitzSignal != null
241                 if (nitzOffsetMightBeBogus(mLatestNitzSignal.mValue)
242                         && isTimeZoneSettingInitialized
243                         && !countryUsesUtc(isoCountryCode, mLatestNitzSignal)) {
244 
245                     // This case means that (1) the device received an NITZ signal that could be
246                     // bogus due to having a zero offset from UTC, (2) the device has had a time
247                     // zone set explicitly and (3) the iso tells us the country is NOT one that uses
248                     // a zero offset. This is interpreted as being NITZ incorrectly reporting a
249                     // local time and not a UTC time. The zone is left as the current device's zone
250                     // setting, and the system clock may be adjusted by taking the NITZ time and
251                     // assuming the current zone setting is correct.
252 
253                     TimeZone zone = TimeZone.getDefault();
254                     if (DBG) {
255                         Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: NITZ looks bogus, maybe using"
256                                 + " current default zone to adjust the system clock,"
257                                 + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
258                                 + " mLatestNitzSignal=" + mLatestNitzSignal
259                                 + " zone=" + zone);
260                     }
261                     zoneId = zone.getID();
262 
263                     if (mNeedCountryCodeForNitz) {
264                         NitzData nitzData = mLatestNitzSignal.mValue;
265                         try {
266                             // Acquire the wakelock as we're reading the elapsed realtime clock
267                             // here.
268                             mWakeLock.acquire();
269 
270                             // Use the time that came with the NITZ offset that we think is bogus:
271                             // we just interpret it as local time.
272                             long ctm = nitzData.getCurrentTimeInMillis();
273                             long delayAdjustedCtm = ctm + (mTimeServiceHelper.elapsedRealtime()
274                                     - mLatestNitzSignal.mElapsedRealtime);
275                             long tzOffset = zone.getOffset(delayAdjustedCtm);
276                             if (DBG) {
277                                 Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
278                                         + " tzOffset=" + tzOffset
279                                         + " delayAdjustedCtm="
280                                         + TimeUtils.logTimeOfDay(delayAdjustedCtm));
281                             }
282                             if (mTimeServiceHelper.isTimeDetectionEnabled()) {
283                                 long timeZoneAdjustedCtm = delayAdjustedCtm - tzOffset;
284                                 String msg = "handleNetworkCountryCodeSet: setting time"
285                                         + " timeZoneAdjustedCtm="
286                                         + TimeUtils.logTimeOfDay(timeZoneAdjustedCtm);
287                                 setAndBroadcastNetworkSetTime(msg, timeZoneAdjustedCtm);
288                             } else {
289                                 // Adjust the saved NITZ time to account for tzOffset.
290                                 mSavedNitzTime = new TimeStampedValue<>(
291                                         mSavedNitzTime.mValue - tzOffset,
292                                         mSavedNitzTime.mElapsedRealtime);
293                                 if (DBG) {
294                                     Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
295                                             + "adjusting time mSavedNitzTime=" + mSavedNitzTime);
296                                 }
297                             }
298                         } finally {
299                             mWakeLock.release();
300                         }
301                     }
302                 } else {
303                     NitzData nitzData = mLatestNitzSignal.mValue;
304                     OffsetResult lookupResult =
305                             mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, isoCountryCode);
306                     if (DBG) {
307                         Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: using"
308                                 + " guessZoneIdByNitzCountry(nitzData, isoCountryCode),"
309                                 + " nitzData=" + nitzData
310                                 + " isoCountryCode=" + isoCountryCode
311                                 + " lookupResult=" + lookupResult);
312                     }
313                     zoneId = lookupResult != null ? lookupResult.zoneId : null;
314                 }
315             }
316             final String tmpLog = "handleNetworkCountryCodeSet:"
317                     + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
318                     + " mLatestNitzSignal=" + mLatestNitzSignal
319                     + " isoCountryCode=" + isoCountryCode
320                     + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
321                     + " zoneId=" + zoneId;
322             mTimeZoneLog.log(tmpLog);
323 
324             if (zoneId != null) {
325                 Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: zoneId != null, zoneId=" + zoneId);
326                 if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
327                     setAndBroadcastNetworkSetTimeZone(zoneId);
328                 } else {
329                     Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: skip changing zone as"
330                             + " isTimeZoneDetectionEnabled() is false");
331                 }
332                 if (mNeedCountryCodeForNitz) {
333                     mSavedTimeZoneId = zoneId;
334                 }
335             } else {
336                 Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: lookupResult == null, do nothing");
337             }
338             mNeedCountryCodeForNitz = false;
339         }
340     }
341 
countryUsesUtc( String isoCountryCode, TimeStampedValue<NitzData> nitzSignal)342     private boolean countryUsesUtc(
343             String isoCountryCode, TimeStampedValue<NitzData> nitzSignal) {
344         return mTimeZoneLookupHelper.countryUsesUtc(
345                 isoCountryCode,
346                 nitzSignal.mValue.getCurrentTimeInMillis());
347     }
348 
349     /**
350      * Informs the {@link NitzStateMachine} that the network has become available.
351      */
handleNetworkAvailable()352     public void handleNetworkAvailable() {
353         if (DBG) {
354             Rlog.d(LOG_TAG, "handleNetworkAvailable: mNitzTimeZoneDetectionSuccessful="
355                     + mNitzTimeZoneDetectionSuccessful
356                     + ", Setting mNitzTimeZoneDetectionSuccessful=false");
357         }
358         mNitzTimeZoneDetectionSuccessful = false;
359     }
360 
361     /**
362      * Informs the {@link NitzStateMachine} that the network has become unavailable.
363      */
handleNetworkUnavailable()364     public void handleNetworkUnavailable() {
365         if (DBG) {
366             Rlog.d(LOG_TAG, "handleNetworkUnavailable");
367         }
368 
369         mGotCountryCode = false;
370         mNitzTimeZoneDetectionSuccessful = false;
371     }
372 
373     /**
374      * Returns {@code true} if the NITZ data looks like it might be incomplete or bogus, i.e. it has
375      * a zero offset from UTC with either no DST information available or a zero DST offset.
376      */
nitzOffsetMightBeBogus(NitzData nitzData)377     private static boolean nitzOffsetMightBeBogus(NitzData nitzData) {
378         return nitzData.getLocalOffsetMillis() == 0 && !nitzData.isDst();
379     }
380 
381     /**
382      * Handle a new NITZ signal being received.
383      */
handleNitzReceived(TimeStampedValue<NitzData> nitzSignal)384     public void handleNitzReceived(TimeStampedValue<NitzData> nitzSignal) {
385         handleTimeZoneFromNitz(nitzSignal);
386         handleTimeFromNitz(nitzSignal);
387     }
388 
handleTimeZoneFromNitz(TimeStampedValue<NitzData> nitzSignal)389     private void handleTimeZoneFromNitz(TimeStampedValue<NitzData> nitzSignal) {
390         try {
391             NitzData newNitzData = nitzSignal.mValue;
392             String iso = mDeviceState.getNetworkCountryIsoForPhone();
393             String zoneId;
394             if (newNitzData.getEmulatorHostTimeZone() != null) {
395                 zoneId = newNitzData.getEmulatorHostTimeZone().getID();
396             } else {
397                 if (!mGotCountryCode) {
398                     zoneId = null;
399                 } else if (!TextUtils.isEmpty(iso)) {
400                     OffsetResult lookupResult =
401                             mTimeZoneLookupHelper.lookupByNitzCountry(newNitzData, iso);
402                     zoneId = lookupResult != null ? lookupResult.zoneId : null;
403                 } else {
404                     // We don't have a valid iso country code.  This is
405                     // most likely because we're on a test network that's
406                     // using a bogus MCC (eg, "001"), so get a TimeZone
407                     // based only on the NITZ parameters.
408                     OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitz(newNitzData);
409                     if (DBG) {
410                         Rlog.d(LOG_TAG, "handleTimeZoneFromNitz: guessZoneIdByNitz returned"
411                                 + " lookupResult=" + lookupResult);
412                     }
413                     zoneId = lookupResult != null ? lookupResult.zoneId : null;
414                 }
415             }
416 
417             if ((zoneId == null)
418                     || mLatestNitzSignal == null
419                     || offsetInfoDiffers(newNitzData, mLatestNitzSignal.mValue)) {
420                 // We got the time before the country, or the zone has changed
421                 // so we don't know how to identify the DST rules yet.  Save
422                 // the information and hope to fix it up later.
423                 mNeedCountryCodeForNitz = true;
424                 mLatestNitzSignal = nitzSignal;
425             }
426 
427             String tmpLog = "handleTimeZoneFromNitz: nitzSignal=" + nitzSignal
428                     + " zoneId=" + zoneId
429                     + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode
430                     + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
431                     + " isTimeZoneDetectionEnabled()="
432                     + mTimeServiceHelper.isTimeZoneDetectionEnabled();
433             if (DBG) {
434                 Rlog.d(LOG_TAG, tmpLog);
435             }
436             mTimeZoneLog.log(tmpLog);
437 
438             if (zoneId != null) {
439                 if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
440                     setAndBroadcastNetworkSetTimeZone(zoneId);
441                 }
442                 mNitzTimeZoneDetectionSuccessful = true;
443                 mSavedTimeZoneId = zoneId;
444             }
445         } catch (RuntimeException ex) {
446             Rlog.e(LOG_TAG, "handleTimeZoneFromNitz: Processing NITZ data"
447                     + " nitzSignal=" + nitzSignal
448                     + " ex=" + ex);
449         }
450     }
451 
offsetInfoDiffers(NitzData one, NitzData two)452     private static boolean offsetInfoDiffers(NitzData one, NitzData two) {
453         return one.getLocalOffsetMillis() != two.getLocalOffsetMillis()
454                 || one.isDst() != two.isDst();
455     }
456 
handleTimeFromNitz(TimeStampedValue<NitzData> nitzSignal)457     private void handleTimeFromNitz(TimeStampedValue<NitzData> nitzSignal) {
458         try {
459             boolean ignoreNitz = mDeviceState.getIgnoreNitz();
460             if (ignoreNitz) {
461                 Rlog.d(LOG_TAG,
462                         "handleTimeFromNitz: Not setting clock because gsm.ignore-nitz is set");
463                 return;
464             }
465 
466             try {
467                 // Acquire the wake lock as we are reading the elapsed realtime clock and system
468                 // clock.
469                 mWakeLock.acquire();
470 
471                 // Validate the nitzTimeSignal to reject obviously bogus elapsedRealtime values.
472                 long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
473                 long millisSinceNitzReceived = elapsedRealtime - nitzSignal.mElapsedRealtime;
474                 if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
475                     if (DBG) {
476                         Rlog.d(LOG_TAG, "handleTimeFromNitz: not setting time, unexpected"
477                                 + " elapsedRealtime=" + elapsedRealtime
478                                 + " nitzSignal=" + nitzSignal);
479                     }
480                     return;
481                 }
482 
483                 // Adjust the NITZ time by the delay since it was received to get the time now.
484                 long adjustedCurrentTimeMillis =
485                         nitzSignal.mValue.getCurrentTimeInMillis() + millisSinceNitzReceived;
486                 long gained = adjustedCurrentTimeMillis - mTimeServiceHelper.currentTimeMillis();
487 
488                 if (mTimeServiceHelper.isTimeDetectionEnabled()) {
489                     String logMsg = "handleTimeFromNitz:"
490                             + " nitzSignal=" + nitzSignal
491                             + " adjustedCurrentTimeMillis=" + adjustedCurrentTimeMillis
492                             + " millisSinceNitzReceived= " + millisSinceNitzReceived
493                             + " gained=" + gained;
494 
495                     if (mSavedNitzTime == null) {
496                         logMsg += ": First update received.";
497                         setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
498                     } else {
499                         long elapsedRealtimeSinceLastSaved = mTimeServiceHelper.elapsedRealtime()
500                                 - mSavedNitzTime.mElapsedRealtime;
501                         int nitzUpdateSpacing = mDeviceState.getNitzUpdateSpacingMillis();
502                         int nitzUpdateDiff = mDeviceState.getNitzUpdateDiffMillis();
503                         if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
504                                 || Math.abs(gained) > nitzUpdateDiff) {
505                             // Either it has been a while since we received an update, or the gain
506                             // is sufficiently large that we want to act on it.
507                             logMsg += ": New update received.";
508                             setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
509                         } else {
510                             if (DBG) {
511                                 Rlog.d(LOG_TAG, logMsg + ": Update throttled.");
512                             }
513 
514                             // Return early. This means that we don't reset the
515                             // mSavedNitzTime for next time and that we may act on more
516                             // NITZ time signals overall but should end up with a system clock that
517                             // tracks NITZ more closely than if we saved throttled values (which
518                             // would reset mSavedNitzTime.elapsedRealtime used to calculate time
519                             // since the last NITZ signal was received).
520                             return;
521                         }
522                     }
523                 }
524 
525                 // Save the last NITZ time signal used so we can return to it later
526                 // if auto-time detection is toggled.
527                 mSavedNitzTime = new TimeStampedValue<>(
528                         adjustedCurrentTimeMillis, nitzSignal.mElapsedRealtime);
529             } finally {
530                 mWakeLock.release();
531             }
532         } catch (RuntimeException ex) {
533             Rlog.e(LOG_TAG, "handleTimeFromNitz: Processing NITZ data"
534                     + " nitzSignal=" + nitzSignal
535                     + " ex=" + ex);
536         }
537     }
538 
setAndBroadcastNetworkSetTimeZone(String zoneId)539     private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
540         if (DBG) {
541             Rlog.d(LOG_TAG, "setAndBroadcastNetworkSetTimeZone: zoneId=" + zoneId);
542         }
543         mTimeServiceHelper.setDeviceTimeZone(zoneId);
544         if (DBG) {
545             Rlog.d(LOG_TAG,
546                     "setAndBroadcastNetworkSetTimeZone: called setDeviceTimeZone()"
547                             + " zoneId=" + zoneId);
548         }
549     }
550 
setAndBroadcastNetworkSetTime(String msg, long time)551     private void setAndBroadcastNetworkSetTime(String msg, long time) {
552         if (!mWakeLock.isHeld()) {
553             Rlog.w(LOG_TAG, "setAndBroadcastNetworkSetTime: Wake lock not held while setting device"
554                     + " time (msg=" + msg + ")");
555         }
556 
557         msg = "setAndBroadcastNetworkSetTime: [Setting time to time=" + time + "]:" + msg;
558         if (DBG) {
559             Rlog.d(LOG_TAG, msg);
560         }
561         mTimeLog.log(msg);
562         mTimeServiceHelper.setDeviceTime(time);
563         TelephonyMetrics.getInstance().writeNITZEvent(mPhone.getPhoneId(), time);
564     }
565 
handleAutoTimeEnabled()566     private void handleAutoTimeEnabled() {
567         if (DBG) {
568             Rlog.d(LOG_TAG, "handleAutoTimeEnabled: Reverting to NITZ Time:"
569                     + " mSavedNitzTime=" + mSavedNitzTime);
570         }
571         if (mSavedNitzTime != null) {
572             try {
573                 // Acquire the wakelock as we're reading the elapsed realtime clock here.
574                 mWakeLock.acquire();
575 
576                 long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
577                 String msg = "mSavedNitzTime: Reverting to NITZ time"
578                         + " elapsedRealtime=" + elapsedRealtime
579                         + " mSavedNitzTime=" + mSavedNitzTime;
580                 long adjustedCurrentTimeMillis =
581                         mSavedNitzTime.mValue + (elapsedRealtime - mSavedNitzTime.mElapsedRealtime);
582                 setAndBroadcastNetworkSetTime(msg, adjustedCurrentTimeMillis);
583             } finally {
584                 mWakeLock.release();
585             }
586         }
587     }
588 
handleAutoTimeZoneEnabled()589     private void handleAutoTimeZoneEnabled() {
590         String tmpLog = "handleAutoTimeZoneEnabled: Reverting to NITZ TimeZone:"
591                 + " mSavedTimeZoneId=" + mSavedTimeZoneId;
592         if (DBG) {
593             Rlog.d(LOG_TAG, tmpLog);
594         }
595         mTimeZoneLog.log(tmpLog);
596         if (mSavedTimeZoneId != null) {
597             setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
598         } else {
599             String iso = mDeviceState.getNetworkCountryIsoForPhone();
600             if (!TextUtils.isEmpty(iso)) {
601                 updateTimeZoneByNetworkCountryCode(iso);
602             }
603         }
604     }
605 
606     /**
607      * Dumps the current in-memory state to the supplied PrintWriter.
608      */
dumpState(PrintWriter pw)609     public void dumpState(PrintWriter pw) {
610         // Time Detection State
611         pw.println(" mSavedTime=" + mSavedNitzTime);
612 
613         // Time Zone Detection State
614         pw.println(" mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz);
615         pw.println(" mLatestNitzSignal=" + mLatestNitzSignal);
616         pw.println(" mGotCountryCode=" + mGotCountryCode);
617         pw.println(" mSavedTimeZoneId=" + mSavedTimeZoneId);
618         pw.println(" mNitzTimeZoneDetectionSuccessful=" + mNitzTimeZoneDetectionSuccessful);
619 
620         // Miscellaneous
621         pw.println(" mWakeLock=" + mWakeLock);
622         pw.flush();
623     }
624 
625     /**
626      * Dumps the time / time zone logs to the supplied IndentingPrintWriter.
627      */
dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args)628     public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
629         ipw.println(" Time Logs:");
630         ipw.increaseIndent();
631         mTimeLog.dump(fd, ipw, args);
632         ipw.decreaseIndent();
633 
634         ipw.println(" Time zone Logs:");
635         ipw.increaseIndent();
636         mTimeZoneLog.dump(fd, ipw, args);
637         ipw.decreaseIndent();
638     }
639 
640     /**
641      * Update time zone by network country code, works well on countries which only have one time
642      * zone or multiple zones with the same offset.
643      *
644      * @param iso Country code from network MCC
645      */
updateTimeZoneByNetworkCountryCode(String iso)646     private void updateTimeZoneByNetworkCountryCode(String iso) {
647         CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
648                 iso, mTimeServiceHelper.currentTimeMillis());
649         if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
650             String logMsg = "updateTimeZoneByNetworkCountryCode: set time"
651                     + " lookupResult=" + lookupResult
652                     + " iso=" + iso;
653             if (DBG) {
654                 Rlog.d(LOG_TAG, logMsg);
655             }
656             mTimeZoneLog.log(logMsg);
657             setAndBroadcastNetworkSetTimeZone(lookupResult.zoneId);
658         } else {
659             if (DBG) {
660                 Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: no good zone for"
661                         + " iso=" + iso
662                         + " lookupResult=" + lookupResult);
663             }
664         }
665     }
666 
667     /**
668      * Get the mNitzTimeZoneDetectionSuccessful flag value.
669      */
getNitzTimeZoneDetectionSuccessful()670     public boolean getNitzTimeZoneDetectionSuccessful() {
671         return mNitzTimeZoneDetectionSuccessful;
672     }
673 
674     /**
675      * Returns the last NITZ data that was cached.
676      */
getCachedNitzData()677     public NitzData getCachedNitzData() {
678         return mLatestNitzSignal != null ? mLatestNitzSignal.mValue : null;
679     }
680 
681     /**
682      * Returns the time zone ID from the most recent time that a time zone could be determined by
683      * this state machine.
684      */
getSavedTimeZoneId()685     public String getSavedTimeZoneId() {
686         return mSavedTimeZoneId;
687     }
688 
689 }
690