1 /*
2  * Copyright 2019 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.nitz;
18 
19 import static org.junit.Assert.fail;
20 
21 import android.app.timedetector.TelephonyTimeSuggestion;
22 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
23 import android.icu.util.Calendar;
24 import android.icu.util.GregorianCalendar;
25 import android.icu.util.TimeZone;
26 import android.os.TimestampedValue;
27 
28 import com.android.internal.telephony.NitzData;
29 import com.android.internal.telephony.NitzStateMachine;
30 import com.android.internal.telephony.NitzStateMachine.DeviceState;
31 
32 /**
33  * An assortment of methods and classes for testing {@link NitzStateMachine} implementations.
34  */
35 final class NitzStateMachineTestSupport {
36 
37     // Values used to when initializing device state but where the value isn't important.
38     static final long ARBITRARY_SYSTEM_CLOCK_TIME = createUtcTime(1977, 1, 1, 12, 0, 0);
39     static final long ARBITRARY_REALTIME_MILLIS = 123456789L;
40     static final String ARBITRARY_DEBUG_INFO = "Test debug info";
41 
42     // A country with a single zone : the zone can be guessed from the country.
43     // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
44     static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
45             .setTimeZone("Europe/London")
46             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
47             .setCountryIso("gb")
48             .buildFrozen();
49 
50     static final String UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID = "Europe/London";
51 
52     // The US is a country that has multiple zones, but there is only one matching time zone at the
53     // time in this scenario: the zone cannot be guessed from the country alone, but can be guessed
54     // from the country + NITZ. The US never uses UTC so it can be used for testing bogus (zero'd
55     // values) NITZ signals.
56     static final Scenario UNIQUE_US_ZONE_SCENARIO1 = new Scenario.Builder()
57             .setTimeZone("America/Los_Angeles")
58             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
59             .setCountryIso("us")
60             .buildFrozen();
61 
62     // An alternative US scenario which also provides a unique time zone answer.
63     static final Scenario UNIQUE_US_ZONE_SCENARIO2 = new Scenario.Builder()
64             .setTimeZone("America/Chicago")
65             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
66             .setCountryIso("us")
67             .buildFrozen();
68 
69     // A non-unique US scenario: the offset information is ambiguous between America/Phoenix and
70     // America/Denver during winter.
71     static final Scenario NON_UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
72             .setTimeZone("America/Denver")
73             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
74             .setCountryIso("us")
75             .buildFrozen();
76     static final String[] NON_UNIQUE_US_ZONE_SCENARIO_ZONES =
77             { "America/Denver", "America/Phoenix" };
78 
79     static final String US_COUNTRY_DEFAULT_ZONE_ID = "America/New_York";
80 
81     // New Zealand is a country with multiple zones, but the default zone has the "boost" modifier
82     // which means that NITZ isn't required to find the zone.
83     static final Scenario NEW_ZEALAND_DEFAULT_SCENARIO = new Scenario.Builder()
84             .setTimeZone("Pacific/Auckland")
85             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
86             .setCountryIso("nz")
87             .buildFrozen();
88     static final Scenario NEW_ZEALAND_OTHER_SCENARIO = new Scenario.Builder()
89             .setTimeZone("Pacific/Chatham")
90             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
91             .setCountryIso("nz")
92             .buildFrozen();
93 
94     static final String NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID = "Pacific/Auckland";
95 
96     // A country with a single zone: the zone can be guessed from the country alone. CZ never uses
97     // UTC so it can be used for testing bogus NITZ signal handling.
98     static final Scenario CZECHIA_SCENARIO = new Scenario.Builder()
99             .setTimeZone("Europe/Prague")
100             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
101             .setCountryIso("cz")
102             .buildFrozen();
103 
104     static final String CZECHIA_COUNTRY_DEFAULT_ZONE_ID = "Europe/Prague";
105 
106     /**
107      * A scenario used during tests. Describes a fictional reality.
108      */
109     static class Scenario {
110 
111         private final boolean mFrozen;
112         private TimeZone mZone;
113         private String mNetworkCountryIsoCode;
114         private long mActualTimeMillis;
115 
Scenario(boolean frozen, long timeMillis, String zoneId, String countryIsoCode)116         Scenario(boolean frozen, long timeMillis, String zoneId, String countryIsoCode) {
117             mFrozen = frozen;
118             mActualTimeMillis = timeMillis;
119             mZone = zone(zoneId);
120             mNetworkCountryIsoCode = countryIsoCode;
121         }
122 
123         /** Creates an NITZ signal to match the scenario. */
createNitzSignal(long elapsedRealtimeClock)124         TimestampedValue<NitzData> createNitzSignal(long elapsedRealtimeClock) {
125             return new TimestampedValue<>(elapsedRealtimeClock, createNitzData());
126         }
127 
128         /** Creates an NITZ signal to match the scenario. */
createNitzData()129         NitzData createNitzData() {
130             int[] offsets = new int[2];
131             mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
132             int zoneOffsetMillis = offsets[0] + offsets[1];
133             return NitzData.createForTests(
134                     zoneOffsetMillis, offsets[1], mActualTimeMillis,
135                     null /* emulatorHostTimeZone */);
136         }
137 
getNetworkCountryIsoCode()138         String getNetworkCountryIsoCode() {
139             return mNetworkCountryIsoCode;
140         }
141 
getTimeZoneId()142         String getTimeZoneId() {
143             return mZone.getID();
144         }
145 
getTimeZone()146         TimeZone getTimeZone() {
147             return mZone;
148         }
149 
incrementTime(long timeIncrementMillis)150         Scenario incrementTime(long timeIncrementMillis) {
151             checkFrozen();
152             mActualTimeMillis += timeIncrementMillis;
153             return this;
154         }
155 
changeCountry(String timeZoneId, String networkCountryIsoCode)156         Scenario changeCountry(String timeZoneId, String networkCountryIsoCode) {
157             checkFrozen();
158             mZone = zone(timeZoneId);
159             mNetworkCountryIsoCode = networkCountryIsoCode;
160             return this;
161         }
162 
mutableCopy()163         Scenario mutableCopy() {
164             return new Scenario(
165                     false /* frozen */, mActualTimeMillis, mZone.getID(), mNetworkCountryIsoCode);
166         }
167 
checkFrozen()168         private void checkFrozen() {
169             if (mFrozen) {
170                 throw new IllegalStateException("Scenario is frozen. Copy first");
171             }
172         }
173 
174         static class Builder {
175 
176             private long mActualTimeMillis;
177             private String mZoneId;
178             private String mCountryIsoCode;
179 
setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay, int minute, int second)180             Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
181                     int minute, int second) {
182                 mActualTimeMillis = createUtcTime(year, monthInYear, day, hourOfDay, minute,
183                         second);
184                 return this;
185             }
186 
setTimeZone(String zoneId)187             Builder setTimeZone(String zoneId) {
188                 mZoneId = zoneId;
189                 return this;
190             }
191 
setCountryIso(String isoCode)192             Builder setCountryIso(String isoCode) {
193                 mCountryIsoCode = isoCode;
194                 return this;
195             }
196 
buildFrozen()197             Scenario buildFrozen() {
198                 return new Scenario(true /* frozen */, mActualTimeMillis, mZoneId, mCountryIsoCode);
199             }
200         }
201     }
202 
203     /** A fake implementation of {@link DeviceState}. */
204     static class FakeDeviceState implements DeviceState {
205 
206         public boolean ignoreNitz;
207         public int nitzUpdateDiffMillis;
208         public int nitzUpdateSpacingMillis;
209         public long elapsedRealtime;
210         public long currentTimeMillis;
211 
FakeDeviceState()212         FakeDeviceState() {
213             // Set sensible defaults fake device state.
214             ignoreNitz = false;
215             nitzUpdateDiffMillis = 2000;
216             nitzUpdateSpacingMillis = 1000 * 60 * 10;
217             elapsedRealtime = ARBITRARY_REALTIME_MILLIS;
218         }
219 
220         @Override
getNitzUpdateSpacingMillis()221         public int getNitzUpdateSpacingMillis() {
222             return nitzUpdateSpacingMillis;
223         }
224 
225         @Override
getNitzUpdateDiffMillis()226         public int getNitzUpdateDiffMillis() {
227             return nitzUpdateDiffMillis;
228         }
229 
230         @Override
getIgnoreNitz()231         public boolean getIgnoreNitz() {
232             return ignoreNitz;
233         }
234 
235         @Override
elapsedRealtime()236         public long elapsedRealtime() {
237             return elapsedRealtime;
238         }
239 
240         @Override
currentTimeMillis()241         public long currentTimeMillis() {
242             return currentTimeMillis;
243         }
244 
simulateTimeIncrement(int timeIncrementMillis)245         void simulateTimeIncrement(int timeIncrementMillis) {
246             if (timeIncrementMillis <= 0) {
247                 fail("elapsedRealtime clock must go forwards");
248             }
249             elapsedRealtime += timeIncrementMillis;
250             currentTimeMillis += timeIncrementMillis;
251         }
252 
253     }
254 
NitzStateMachineTestSupport()255     private NitzStateMachineTestSupport() {}
256 
createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute, int second)257     private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
258             int second) {
259         Calendar cal = new GregorianCalendar(zone("Etc/UTC"));
260         cal.clear();
261         cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
262         return cal.getTimeInMillis();
263     }
264 
createEmptyTimeZoneSuggestion(int slotIndex)265     static TelephonyTimeZoneSuggestion createEmptyTimeZoneSuggestion(int slotIndex) {
266         return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
267                 .addDebugInfo("Test")
268                 .build();
269     }
270 
createEmptyTimeSuggestion(int slotIndex)271     static TelephonyTimeSuggestion createEmptyTimeSuggestion(int slotIndex) {
272         return new TelephonyTimeSuggestion.Builder(slotIndex)
273                 .addDebugInfo("Test")
274                 .build();
275     }
276 
createTimeSuggestionFromNitzSignal( int slotIndex, TimestampedValue<NitzData> nitzSignal)277     static TelephonyTimeSuggestion createTimeSuggestionFromNitzSignal(
278             int slotIndex, TimestampedValue<NitzData> nitzSignal) {
279         return new TelephonyTimeSuggestion.Builder(slotIndex)
280                 .setUtcTime(createTimeSignalFromNitzSignal(nitzSignal))
281                 .addDebugInfo("Test")
282                 .build();
283     }
284 
createTimeSignalFromNitzSignal( TimestampedValue<NitzData> nitzSignal)285     private static TimestampedValue<Long> createTimeSignalFromNitzSignal(
286             TimestampedValue<NitzData> nitzSignal) {
287         return new TimestampedValue<>(
288                 nitzSignal.getReferenceTimeMillis(),
289                 nitzSignal.getValue().getCurrentTimeInMillis());
290     }
291 
zone(String zoneId)292     private static TimeZone zone(String zoneId) {
293         TimeZone timeZone = TimeZone.getFrozenTimeZone(zoneId);
294         if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
295             fail(zoneId + " is not a valid zone");
296         }
297         return timeZone;
298     }
299 }
300