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