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