Home | History | Annotate | Download | only in telephony
      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 static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertNull;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.mockito.ArgumentMatchers.any;
     24 import static org.mockito.ArgumentMatchers.anyLong;
     25 import static org.mockito.Mockito.atLeast;
     26 import static org.mockito.Mockito.clearInvocations;
     27 import static org.mockito.Mockito.times;
     28 import static org.mockito.Mockito.verify;
     29 import static org.mockito.Mockito.verifyNoMoreInteractions;
     30 import static org.mockito.Mockito.when;
     31 
     32 import android.icu.util.Calendar;
     33 import android.icu.util.GregorianCalendar;
     34 import android.icu.util.TimeZone;
     35 
     36 import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
     37 import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
     38 import com.android.internal.telephony.util.TimeStampedValue;
     39 
     40 import org.junit.After;
     41 import org.junit.Before;
     42 import org.junit.Test;
     43 import org.mockito.ArgumentCaptor;
     44 import org.mockito.Mock;
     45 
     46 public class NitzStateMachineTest extends TelephonyTest {
     47 
     48     @Mock
     49     private NitzStateMachine.DeviceState mDeviceState;
     50 
     51     @Mock
     52     private TimeServiceHelper mTimeServiceHelper;
     53 
     54     private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
     55 
     56     private NitzStateMachine mNitzStateMachine;
     57 
     58     @Before
     59     public void setUp() throws Exception {
     60         logd("NitzStateMachineTest +Setup!");
     61         super.setUp("NitzStateMachineTest");
     62 
     63         // In tests we use the real TimeZoneLookupHelper.
     64         mRealTimeZoneLookupHelper = new TimeZoneLookupHelper();
     65         mNitzStateMachine = new NitzStateMachine(
     66                 mPhone, mTimeServiceHelper, mDeviceState, mRealTimeZoneLookupHelper);
     67 
     68         logd("ServiceStateTrackerTest -Setup!");
     69     }
     70 
     71     @After
     72     public void tearDown() throws Exception {
     73         checkNoUnverifiedSetOperations(mTimeServiceHelper);
     74 
     75         super.tearDown();
     76     }
     77 
     78     // A country that has multiple zones, but there is only one matching time zone at the time :
     79     // the zone cannot be guessed from the country alone, but can be guessed from the country +
     80     // NITZ.
     81     private static final Scenario UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
     82             .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
     83             .setInitialDeviceRealtimeMillis(123456789L)
     84             .setTimeZone("America/Los_Angeles")
     85             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
     86             .setCountryIso("us")
     87             .build();
     88 
     89     @Test
     90     public void test_uniqueUsZone_Assumptions() {
     91         // Check we'll get the expected behavior from TimeZoneLookupHelper.
     92 
     93         // allZonesHaveSameOffset == false, so we shouldn't pick an arbitrary zone.
     94         CountryResult expectedCountryLookupResult = new CountryResult(
     95                 "America/New_York", false /* allZonesHaveSameOffset */,
     96                 UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
     97         CountryResult actualCountryLookupResult =
     98                 mRealTimeZoneLookupHelper.lookupByCountry(
     99                         UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode(),
    100                         UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
    101         assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
    102 
    103         // isOnlyMatch == true, so the combination of country + NITZ should be enough.
    104         OffsetResult expectedLookupResult =
    105                 new OffsetResult("America/Los_Angeles", true /* isOnlyMatch */);
    106         OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
    107                 UNIQUE_US_ZONE_SCENARIO.getNitzSignal().mValue,
    108                 UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
    109         assertEquals(expectedLookupResult, actualLookupResult);
    110     }
    111 
    112     // A country with a single zone : the zone can be guessed from the country.
    113     private static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
    114             .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
    115             .setInitialDeviceRealtimeMillis(123456789L)
    116             .setTimeZone("Europe/London")
    117             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
    118             .setCountryIso("gb")
    119             .build();
    120 
    121     @Test
    122     public void test_unitedKingdom_Assumptions() {
    123         // Check we'll get the expected behavior from TimeZoneLookupHelper.
    124 
    125         // allZonesHaveSameOffset == true (not only that, there is only one zone), so we can pick
    126         // the zone knowing only the country.
    127         CountryResult expectedCountryLookupResult = new CountryResult(
    128                 "Europe/London", true /* allZonesHaveSameOffset */,
    129                 UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
    130         CountryResult actualCountryLookupResult =
    131                 mRealTimeZoneLookupHelper.lookupByCountry(
    132                         UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode(),
    133                         UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
    134         assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
    135 
    136         OffsetResult expectedLookupResult =
    137                 new OffsetResult("Europe/London", true /* isOnlyMatch */);
    138         OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
    139                 UNITED_KINGDOM_SCENARIO.getNitzSignal().mValue,
    140                 UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode());
    141         assertEquals(expectedLookupResult, actualLookupResult);
    142     }
    143 
    144     @Test
    145     public void test_uniqueUsZone_timeEnabledTimeZoneEnabled_countryThenNitz() throws Exception {
    146         Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
    147         Device device = new DeviceBuilder()
    148                 .setClocksFromScenario(scenario)
    149                 .setTimeDetectionEnabled(true)
    150                 .setTimeZoneDetectionEnabled(true)
    151                 .setTimeZoneSettingInitialized(false)
    152                 .initialize();
    153         Script script = new Script(device);
    154 
    155         int clockIncrement = 1250;
    156         long expectedTimeMillis = scenario.getActualTimeMillis() + clockIncrement;
    157         script.countryReceived(scenario.getNetworkCountryIsoCode())
    158                 // Country won't be enough for time zone detection.
    159                 .verifyNothingWasSetAndReset()
    160                 // Increment the clock so we can tell the time was adjusted correctly when set.
    161                 .incrementClocks(clockIncrement)
    162                 .nitzReceived(scenario.getNitzSignal())
    163                 // Country + NITZ is enough for both time + time zone detection.
    164                 .verifyTimeAndZoneSetAndReset(expectedTimeMillis, scenario.getTimeZoneId());
    165 
    166         // Check NitzStateMachine state.
    167         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    168         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    169         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    170     }
    171 
    172     @Test
    173     public void test_unitedKingdom_timeEnabledTimeZoneEnabled_countryThenNitz() throws Exception {
    174         Scenario scenario = UNITED_KINGDOM_SCENARIO;
    175         Device device = new DeviceBuilder()
    176                 .setClocksFromScenario(scenario)
    177                 .setTimeDetectionEnabled(true)
    178                 .setTimeZoneDetectionEnabled(true)
    179                 .setTimeZoneSettingInitialized(false)
    180                 .initialize();
    181         Script script = new Script(device);
    182 
    183         int clockIncrement = 1250;
    184         long expectedTimeMillis = scenario.getActualTimeMillis() + clockIncrement;
    185         script.countryReceived(scenario.getNetworkCountryIsoCode())
    186                 // Country alone is enough to guess the time zone.
    187                 .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
    188                 // Increment the clock so we can tell the time was adjusted correctly when set.
    189                 .incrementClocks(clockIncrement)
    190                 .nitzReceived(scenario.getNitzSignal())
    191                 // Country + NITZ is enough for both time + time zone detection.
    192                 .verifyTimeAndZoneSetAndReset(expectedTimeMillis, scenario.getTimeZoneId());
    193 
    194         // Check NitzStateMachine state.
    195         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    196         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    197         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    198     }
    199 
    200     @Test
    201     public void test_uniqueUsZone_timeEnabledTimeZoneDisabled_countryThenNitz() throws Exception {
    202         Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
    203         Device device = new DeviceBuilder()
    204                 .setClocksFromScenario(scenario)
    205                 .setTimeDetectionEnabled(true)
    206                 .setTimeZoneDetectionEnabled(false)
    207                 .setTimeZoneSettingInitialized(false)
    208                 .initialize();
    209         Script script = new Script(device);
    210 
    211         int clockIncrement = 1250;
    212         script.countryReceived(scenario.getNetworkCountryIsoCode())
    213                 // Country is not enough to guess the time zone and time zone detection is disabled.
    214                 .verifyNothingWasSetAndReset()
    215                 // Increment the clock so we can tell the time was adjusted correctly when set.
    216                 .incrementClocks(clockIncrement)
    217                 .nitzReceived(scenario.getNitzSignal())
    218                 // Time zone detection is disabled, but time should be set from NITZ.
    219                 .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis() + clockIncrement);
    220 
    221         // Check NitzStateMachine state.
    222         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    223         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    224         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    225     }
    226 
    227     @Test
    228     public void test_unitedKingdom_timeEnabledTimeZoneDisabled_countryThenNitz()
    229             throws Exception {
    230         Scenario scenario = UNITED_KINGDOM_SCENARIO;
    231         Device device = new DeviceBuilder()
    232                 .setClocksFromScenario(scenario)
    233                 .setTimeDetectionEnabled(true)
    234                 .setTimeZoneDetectionEnabled(false)
    235                 .setTimeZoneSettingInitialized(false)
    236                 .initialize();
    237         Script script = new Script(device);
    238 
    239         int clockIncrement = 1250;
    240         script.countryReceived(scenario.getNetworkCountryIsoCode())
    241                 // Country alone would be enough for time zone detection, but it's disabled.
    242                 .verifyNothingWasSetAndReset()
    243                 // Increment the clock so we can tell the time was adjusted correctly when set.
    244                 .incrementClocks(clockIncrement)
    245                 .nitzReceived(scenario.getNitzSignal())
    246                 // Time zone detection is disabled, but time should be set from NITZ.
    247                 .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis() + clockIncrement);
    248 
    249         // Check NitzStateMachine state.
    250         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    251         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    252         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    253     }
    254 
    255     @Test
    256     public void test_uniqueUsZone_timeDisabledTimeZoneEnabled_countryThenNitz() throws Exception {
    257         Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
    258         Device device = new DeviceBuilder()
    259                 .setClocksFromScenario(scenario)
    260                 .setTimeDetectionEnabled(false)
    261                 .setTimeZoneDetectionEnabled(true)
    262                 .setTimeZoneSettingInitialized(false)
    263                 .initialize();
    264         Script script = new Script(device);
    265 
    266         script.countryReceived(scenario.getNetworkCountryIsoCode())
    267                 // Country won't be enough for time zone detection.
    268                 .verifyNothingWasSetAndReset()
    269                 .nitzReceived(scenario.getNitzSignal())
    270                 // Time detection is disabled, but time zone should be detected from country + NITZ.
    271                 .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
    272 
    273         // Check NitzStateMachine state.
    274         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    275         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    276         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    277     }
    278 
    279     @Test
    280     public void test_unitedKingdom_timeDisabledTimeZoneEnabled_countryThenNitz() throws Exception {
    281         Scenario scenario = UNITED_KINGDOM_SCENARIO;
    282         Device device = new DeviceBuilder()
    283                 .setClocksFromScenario(scenario)
    284                 .setTimeDetectionEnabled(false)
    285                 .setTimeZoneDetectionEnabled(true)
    286                 .setTimeZoneSettingInitialized(false)
    287                 .initialize();
    288         Script script = new Script(device);
    289 
    290         script.countryReceived(scenario.getNetworkCountryIsoCode())
    291                 // Country alone is enough to detect time zone.
    292                 .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
    293                 .nitzReceived(scenario.getNitzSignal())
    294                 // Time detection is disabled, so we don't set the clock from NITZ.
    295                 .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
    296 
    297         // Check NitzStateMachine state.
    298         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    299         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    300         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    301     }
    302 
    303     @Test
    304     public void test_uniqueUsZone_timeDisabledTimeZoneDisabled_countryThenNitz() throws Exception {
    305         Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
    306         Device device = new DeviceBuilder()
    307                 .setClocksFromScenario(scenario)
    308                 .setTimeDetectionEnabled(false)
    309                 .setTimeZoneDetectionEnabled(false)
    310                 .setTimeZoneSettingInitialized(false)
    311                 .initialize();
    312         Script script = new Script(device);
    313 
    314         script.countryReceived(scenario.getNetworkCountryIsoCode())
    315                 // Time and time zone detection is disabled.
    316                 .verifyNothingWasSetAndReset()
    317                 .nitzReceived(scenario.getNitzSignal())
    318                 // Time and time zone detection is disabled.
    319                 .verifyNothingWasSetAndReset();
    320 
    321         // Check NitzStateMachine state.
    322         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    323         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    324         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    325     }
    326 
    327     @Test
    328     public void test_unitedKingdom_timeDisabledTimeZoneDisabled_countryThenNitz() throws Exception {
    329         Scenario scenario = UNITED_KINGDOM_SCENARIO;
    330         Device device = new DeviceBuilder()
    331                 .setClocksFromScenario(scenario)
    332                 .setTimeDetectionEnabled(false)
    333                 .setTimeZoneDetectionEnabled(false)
    334                 .setTimeZoneSettingInitialized(false)
    335                 .initialize();
    336         Script script = new Script(device);
    337 
    338         script.countryReceived(scenario.getNetworkCountryIsoCode())
    339                 // Time and time zone detection is disabled.
    340                 .verifyNothingWasSetAndReset()
    341                 .nitzReceived(scenario.getNitzSignal())
    342                 // Time and time zone detection is disabled.
    343                 .verifyNothingWasSetAndReset();
    344 
    345         // Check NitzStateMachine state.
    346         assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    347         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    348         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    349     }
    350 
    351     @Test
    352     public void test_uniqueUsZone_timeDisabledTimeZoneEnabled_nitzThenCountry() throws Exception {
    353         Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
    354         Device device = new DeviceBuilder()
    355                 .setClocksFromScenario(scenario)
    356                 .setTimeDetectionEnabled(false)
    357                 .setTimeZoneDetectionEnabled(true)
    358                 .setTimeZoneSettingInitialized(false)
    359                 .initialize();
    360         Script script = new Script(device);
    361 
    362         // Simulate receiving an NITZ signal.
    363         script.nitzReceived(scenario.getNitzSignal())
    364                 // The NITZ alone isn't enough to detect a time zone.
    365                 .verifyNothingWasSetAndReset();
    366 
    367         // Check NitzStateMachine state.
    368         assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    369         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    370         assertNull(mNitzStateMachine.getSavedTimeZoneId());
    371 
    372         // Simulate the country code becoming known.
    373         script.countryReceived(scenario.getNetworkCountryIsoCode())
    374                 // The NITZ + country is enough to detect the time zone.
    375                 .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
    376 
    377         // Check NitzStateMachine state.
    378         // TODO(nfuller): The following line should probably be assertTrue but the logic under test
    379         // may be buggy. Look at whether it needs to change.
    380         assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    381         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    382         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    383     }
    384 
    385     @Test
    386     public void test_unitedKingdom_timeDisabledTimeZoneEnabled_nitzThenCountry() throws Exception {
    387         Scenario scenario = UNITED_KINGDOM_SCENARIO;
    388         Device device = new DeviceBuilder()
    389                 .setClocksFromScenario(scenario)
    390                 .setTimeDetectionEnabled(false)
    391                 .setTimeZoneDetectionEnabled(true)
    392                 .setTimeZoneSettingInitialized(false)
    393                 .initialize();
    394         Script script = new Script(device);
    395 
    396         // Simulate receiving an NITZ signal.
    397         script.nitzReceived(scenario.getNitzSignal())
    398                 // The NITZ alone isn't enough to detect a time zone.
    399                 .verifyNothingWasSetAndReset();
    400 
    401         // Check NitzStateMachine state.
    402         assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    403         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    404         assertNull(mNitzStateMachine.getSavedTimeZoneId());
    405 
    406         // Simulate the country code becoming known.
    407         script.countryReceived(scenario.getNetworkCountryIsoCode());
    408 
    409         // The NITZ + country is enough to detect the time zone.
    410         // NOTE: setting the timezone happens twice because of a quirk in NitzStateMachine: it
    411         // handles the country lookup / set, then combines the country with the NITZ state and does
    412         // another lookup / set. We shouldn't require it is set twice but we do for simplicity.
    413         script.verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2 /* times */);
    414 
    415         // Check NitzStateMachine state.
    416         // TODO(nfuller): The following line should probably be assertTrue but the logic under test
    417         // may be buggy. Look at whether it needs to change.
    418         assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
    419         assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
    420         assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
    421     }
    422 
    423     private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
    424             int second) {
    425         Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
    426         cal.clear();
    427         cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
    428         return cal.getTimeInMillis();
    429     }
    430 
    431     /**
    432      * A helper class for common test operations involving a device.
    433      */
    434     class Script {
    435         private final Device mDevice;
    436 
    437         Script(Device device) {
    438             this.mDevice = device;
    439         }
    440 
    441         Script countryReceived(String countryIsoCode) {
    442             mDevice.networkCountryKnown(countryIsoCode);
    443             return this;
    444         }
    445 
    446         Script nitzReceived(TimeStampedValue<NitzData> nitzSignal) {
    447             mDevice.nitzSignalReceived(nitzSignal);
    448             return this;
    449         }
    450 
    451         Script incrementClocks(int clockIncrement) {
    452             mDevice.incrementClocks(clockIncrement);
    453             return this;
    454         }
    455 
    456         Script verifyNothingWasSetAndReset() {
    457             mDevice.verifyTimeZoneWasNotSet();
    458             mDevice.verifyTimeWasNotSet();
    459             mDevice.checkNoUnverifiedSetOperations();
    460             mDevice.resetInvocations();
    461             return this;
    462         }
    463 
    464         Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId, int times) {
    465             mDevice.verifyTimeZoneWasSet(timeZoneId, times);
    466             mDevice.verifyTimeWasNotSet();
    467             mDevice.checkNoUnverifiedSetOperations();
    468             mDevice.resetInvocations();
    469             return this;
    470         }
    471 
    472         Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId) {
    473             return verifyOnlyTimeZoneWasSetAndReset(timeZoneId, 1);
    474         }
    475 
    476         Script verifyOnlyTimeWasSetAndReset(long expectedTimeMillis) {
    477             mDevice.verifyTimeZoneWasNotSet();
    478             mDevice.verifyTimeWasSet(expectedTimeMillis);
    479             mDevice.checkNoUnverifiedSetOperations();
    480             mDevice.resetInvocations();
    481             return this;
    482         }
    483 
    484         Script verifyTimeAndZoneSetAndReset(long expectedTimeMillis, String timeZoneId) {
    485             mDevice.verifyTimeZoneWasSet(timeZoneId);
    486             mDevice.verifyTimeWasSet(expectedTimeMillis);
    487             mDevice.checkNoUnverifiedSetOperations();
    488             mDevice.resetInvocations();
    489             return this;
    490         }
    491 
    492         Script reset() {
    493             mDevice.checkNoUnverifiedSetOperations();
    494             mDevice.resetInvocations();
    495             return this;
    496         }
    497     }
    498 
    499     /**
    500      * An abstraction of a device for use in telephony time zone detection tests. It can be used to
    501      * retrieve device state, modify device state and verify changes.
    502      */
    503     class Device {
    504 
    505         private final long mInitialSystemClockMillis;
    506         private final long mInitialRealtimeMillis;
    507         private final boolean mTimeDetectionEnabled;
    508         private final boolean mTimeZoneDetectionEnabled;
    509         private final boolean mTimeZoneSettingInitialized;
    510 
    511         Device(long initialSystemClockMillis, long initialRealtimeMillis,
    512                 boolean timeDetectionEnabled, boolean timeZoneDetectionEnabled,
    513                 boolean timeZoneSettingInitialized) {
    514             mInitialSystemClockMillis = initialSystemClockMillis;
    515             mInitialRealtimeMillis = initialRealtimeMillis;
    516             mTimeDetectionEnabled = timeDetectionEnabled;
    517             mTimeZoneDetectionEnabled = timeZoneDetectionEnabled;
    518             mTimeZoneSettingInitialized = timeZoneSettingInitialized;
    519         }
    520 
    521         void initialize() {
    522             // Set initial configuration.
    523             when(mDeviceState.getIgnoreNitz()).thenReturn(false);
    524             when(mDeviceState.getNitzUpdateDiffMillis()).thenReturn(2000);
    525             when(mDeviceState.getNitzUpdateSpacingMillis()).thenReturn(1000 * 60 * 10);
    526 
    527             // Simulate the country not being known.
    528             when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn("");
    529 
    530             when(mTimeServiceHelper.elapsedRealtime()).thenReturn(mInitialRealtimeMillis);
    531             when(mTimeServiceHelper.currentTimeMillis()).thenReturn(mInitialSystemClockMillis);
    532             when(mTimeServiceHelper.isTimeDetectionEnabled()).thenReturn(mTimeDetectionEnabled);
    533             when(mTimeServiceHelper.isTimeZoneDetectionEnabled())
    534                     .thenReturn(mTimeZoneDetectionEnabled);
    535             when(mTimeServiceHelper.isTimeZoneSettingInitialized())
    536                     .thenReturn(mTimeZoneSettingInitialized);
    537         }
    538 
    539         void networkCountryKnown(String countryIsoCode) {
    540             when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(countryIsoCode);
    541             mNitzStateMachine.handleNetworkCountryCodeSet(true);
    542         }
    543 
    544         void incrementClocks(int millis) {
    545             long currentElapsedRealtime = mTimeServiceHelper.elapsedRealtime();
    546             when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentElapsedRealtime + millis);
    547             long currentTimeMillis = mTimeServiceHelper.currentTimeMillis();
    548             when(mTimeServiceHelper.currentTimeMillis()).thenReturn(currentTimeMillis + millis);
    549         }
    550 
    551         void nitzSignalReceived(TimeStampedValue<NitzData> nitzSignal) {
    552             mNitzStateMachine.handleNitzReceived(nitzSignal);
    553         }
    554 
    555         void verifyTimeZoneWasNotSet() {
    556             verify(mTimeServiceHelper, times(0)).setDeviceTimeZone(any(String.class));
    557         }
    558 
    559         void verifyTimeZoneWasSet(String timeZoneId) {
    560             verifyTimeZoneWasSet(timeZoneId, 1 /* times */);
    561         }
    562 
    563         void verifyTimeZoneWasSet(String timeZoneId, int times) {
    564             verify(mTimeServiceHelper, times(times)).setDeviceTimeZone(timeZoneId);
    565         }
    566 
    567         void verifyTimeWasNotSet() {
    568             verify(mTimeServiceHelper, times(0)).setDeviceTime(anyLong());
    569         }
    570 
    571         void verifyTimeWasSet(long expectedTimeMillis) {
    572             ArgumentCaptor<Long> timeServiceTimeCaptor = ArgumentCaptor.forClass(Long.TYPE);
    573             verify(mTimeServiceHelper, times(1)).setDeviceTime(timeServiceTimeCaptor.capture());
    574             assertEquals(expectedTimeMillis, (long) timeServiceTimeCaptor.getValue());
    575         }
    576 
    577         /**
    578          * Used after calling verify... methods to reset expectations.
    579          */
    580         void resetInvocations() {
    581             clearInvocations(mTimeServiceHelper);
    582         }
    583 
    584         void checkNoUnverifiedSetOperations() {
    585             NitzStateMachineTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
    586         }
    587     }
    588 
    589     /** A class used to construct a Device. */
    590     class DeviceBuilder {
    591 
    592         private long mInitialSystemClock;
    593         private long mInitialRealtimeMillis;
    594         private boolean mTimeDetectionEnabled;
    595         private boolean mTimeZoneDetectionEnabled;
    596         private boolean mTimeZoneSettingInitialized;
    597 
    598         Device initialize() {
    599             Device device = new Device(mInitialSystemClock, mInitialRealtimeMillis,
    600                     mTimeDetectionEnabled, mTimeZoneDetectionEnabled, mTimeZoneSettingInitialized);
    601             device.initialize();
    602             return device;
    603         }
    604 
    605         DeviceBuilder setTimeDetectionEnabled(boolean enabled) {
    606             mTimeDetectionEnabled = enabled;
    607             return this;
    608         }
    609 
    610         DeviceBuilder setTimeZoneDetectionEnabled(boolean enabled) {
    611             mTimeZoneDetectionEnabled = enabled;
    612             return this;
    613         }
    614 
    615         DeviceBuilder setTimeZoneSettingInitialized(boolean initialized) {
    616             mTimeZoneSettingInitialized = initialized;
    617             return this;
    618         }
    619 
    620         DeviceBuilder setClocksFromScenario(Scenario scenario) {
    621             mInitialRealtimeMillis = scenario.getInitialRealTimeMillis();
    622             mInitialSystemClock = scenario.getInitialSystemClockMillis();
    623             return this;
    624         }
    625     }
    626 
    627     /**
    628      * A scenario used during tests. Describes a fictional reality.
    629      */
    630     static class Scenario {
    631 
    632         private final long mInitialDeviceSystemClockMillis;
    633         private final long mInitialDeviceRealtimeMillis;
    634         private final long mActualTimeMillis;
    635         private final TimeZone mZone;
    636         private final String mNetworkCountryIsoCode;
    637 
    638         private TimeStampedValue<NitzData> mNitzSignal;
    639 
    640         Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis,
    641                 String zoneId, String countryIsoCode) {
    642             mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
    643             mActualTimeMillis = timeMillis;
    644             mInitialDeviceRealtimeMillis = elapsedRealtime;
    645             mZone = TimeZone.getTimeZone(zoneId);
    646             mNetworkCountryIsoCode = countryIsoCode;
    647         }
    648 
    649         TimeStampedValue<NitzData> getNitzSignal() {
    650             if (mNitzSignal == null) {
    651                 int[] offsets = new int[2];
    652                 mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
    653                 int zoneOffsetMillis = offsets[0] + offsets[1];
    654                 NitzData nitzData = NitzData
    655                         .createForTests(zoneOffsetMillis, offsets[1], mActualTimeMillis, null);
    656                 mNitzSignal = new TimeStampedValue<>(nitzData, mInitialDeviceRealtimeMillis);
    657             }
    658             return mNitzSignal;
    659         }
    660 
    661         long getInitialRealTimeMillis() {
    662             return mInitialDeviceRealtimeMillis;
    663         }
    664 
    665         long getInitialSystemClockMillis() {
    666             return mInitialDeviceSystemClockMillis;
    667         }
    668 
    669         String getNetworkCountryIsoCode() {
    670             return mNetworkCountryIsoCode;
    671         }
    672 
    673         String getTimeZoneId() {
    674             return mZone.getID();
    675         }
    676 
    677         long getActualTimeMillis() {
    678             return mActualTimeMillis;
    679         }
    680 
    681         static class Builder {
    682 
    683             private long mInitialDeviceSystemClockMillis;
    684             private long mInitialDeviceRealtimeMillis;
    685             private long mActualTimeMillis;
    686             private String mZoneId;
    687             private String mCountryIsoCode;
    688 
    689             Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
    690                     int hourOfDay, int minute, int second) {
    691                 mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
    692                         minute, second);
    693                 return this;
    694             }
    695 
    696             Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
    697                 mInitialDeviceRealtimeMillis = realtimeMillis;
    698                 return this;
    699             }
    700 
    701             Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
    702                     int minute, int second) {
    703                 mActualTimeMillis = createUtcTime(year, monthInYear, day, hourOfDay, minute,
    704                         second);
    705                 return this;
    706             }
    707 
    708             Builder setTimeZone(String zoneId) {
    709                 mZoneId = zoneId;
    710                 return this;
    711             }
    712 
    713             Builder setCountryIso(String isoCode) {
    714                 mCountryIsoCode = isoCode;
    715                 return this;
    716             }
    717 
    718             Scenario build() {
    719                 return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
    720                         mActualTimeMillis, mZoneId, mCountryIsoCode);
    721             }
    722         }
    723     }
    724 
    725     /**
    726      * Confirms all mTimeServiceHelper side effects were verified.
    727      */
    728     private static void checkNoUnverifiedSetOperations(TimeServiceHelper mTimeServiceHelper) {
    729         // We don't care about current auto time / time zone state retrievals / listening so we can
    730         // use "at least 0" times to indicate they don't matter.
    731         verify(mTimeServiceHelper, atLeast(0)).setListener(any());
    732         verify(mTimeServiceHelper, atLeast(0)).isTimeDetectionEnabled();
    733         verify(mTimeServiceHelper, atLeast(0)).isTimeZoneDetectionEnabled();
    734         verify(mTimeServiceHelper, atLeast(0)).isTimeZoneSettingInitialized();
    735         verify(mTimeServiceHelper, atLeast(0)).elapsedRealtime();
    736         verify(mTimeServiceHelper, atLeast(0)).currentTimeMillis();
    737         verifyNoMoreInteractions(mTimeServiceHelper);
    738     }
    739 }
    740