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