blob: 62f1433f7907bba1cf3cceb7ed293397f79fca6a [file] [log] [blame]
Neil Fuller4773b9d2018-06-08 18:44:49 +01001/*
2 * Copyright (C) 2018 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
17package com.android.server.timedetector;
18
Neil Fuller4980bbc2018-06-12 21:06:20 +010019import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertFalse;
21import static org.junit.Assert.assertNotNull;
22import static org.junit.Assert.assertNull;
23import static org.junit.Assert.assertTrue;
24import static org.junit.Assert.fail;
Neil Fuller4773b9d2018-06-08 18:44:49 +010025
26import android.app.timedetector.TimeSignal;
Neil Fuller4980bbc2018-06-12 21:06:20 +010027import android.content.Intent;
28import android.icu.util.Calendar;
29import android.icu.util.GregorianCalendar;
30import android.icu.util.TimeZone;
Neil Fuller4773b9d2018-06-08 18:44:49 +010031import android.support.test.runner.AndroidJUnit4;
32import android.util.TimestampedValue;
33
34import org.junit.Before;
35import org.junit.Test;
36import org.junit.runner.RunWith;
37
38@RunWith(AndroidJUnit4.class)
39public class SimpleTimeZoneDetectorStrategyTest {
40
Neil Fuller4980bbc2018-06-12 21:06:20 +010041 private static final Scenario SCENARIO_1 = new Scenario.Builder()
42 .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
43 .setInitialDeviceRealtimeMillis(123456789L)
44 .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
45 .build();
Neil Fuller4773b9d2018-06-08 18:44:49 +010046
Neil Fuller4980bbc2018-06-12 21:06:20 +010047 private Script mScript;
Neil Fuller4773b9d2018-06-08 18:44:49 +010048
49 @Before
50 public void setUp() {
Neil Fuller4980bbc2018-06-12 21:06:20 +010051 mScript = new Script();
Neil Fuller4773b9d2018-06-08 18:44:49 +010052 }
53
54 @Test
Neil Fuller4980bbc2018-06-12 21:06:20 +010055 public void testSuggestTime_nitz_timeDetectionEnabled() {
56 Scenario scenario = SCENARIO_1;
57 mScript.pokeFakeClocks(scenario)
58 .pokeTimeDetectionEnabled(true);
Neil Fuller4773b9d2018-06-08 18:44:49 +010059
Neil Fuller4980bbc2018-06-12 21:06:20 +010060 TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
61 final int clockIncrement = 1000;
62 long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
Neil Fuller4773b9d2018-06-08 18:44:49 +010063
Neil Fuller4980bbc2018-06-12 21:06:20 +010064 mScript.simulateTimePassing(clockIncrement)
65 .simulateTimeSignalReceived(timeSignal)
66 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
67 }
68
69 @Test
70 public void testSuggestTime_systemClockThreshold() {
71 Scenario scenario = SCENARIO_1;
72 final int systemClockUpdateThresholdMillis = 1000;
73 mScript.pokeFakeClocks(scenario)
74 .pokeThresholds(systemClockUpdateThresholdMillis)
75 .pokeTimeDetectionEnabled(true);
76
77 TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
78 TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
79
80 final int clockIncrement = 100;
81 // Increment the the device clocks to simulate the passage of time.
82 mScript.simulateTimePassing(clockIncrement);
83
84 long expectSystemClockMillis1 =
85 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
86
87 // Send the first time signal. It should be used.
88 mScript.simulateTimeSignalReceived(timeSignal1)
89 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
90
91 // Now send another time signal, but one that is too similar to the last one and should be
92 // ignored.
93 int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
94 TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
95 mScript.peekElapsedRealtimeMillis(),
96 mScript.peekSystemClockMillis() + underThresholdMillis);
97 TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
98 mScript.simulateTimePassing(clockIncrement)
99 .simulateTimeSignalReceived(timeSignal2)
100 .verifySystemClockWasNotSetAndResetCallTracking();
101
102 // Now send another time signal, but one that is on the threshold and so should be used.
103 TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
104 mScript.peekElapsedRealtimeMillis(),
105 mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
106
107 TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
108 mScript.simulateTimePassing(clockIncrement);
109
110 long expectSystemClockMillis3 =
111 TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
112
113 mScript.simulateTimeSignalReceived(timeSignal3)
114 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
115 }
116
117 @Test
118 public void testSuggestTime_nitz_timeDetectionDisabled() {
119 Scenario scenario = SCENARIO_1;
120 mScript.pokeFakeClocks(scenario)
121 .pokeTimeDetectionEnabled(false);
122
123 TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
124 mScript.simulateTimeSignalReceived(timeSignal)
125 .verifySystemClockWasNotSetAndResetCallTracking();
126 }
127
128 @Test
129 public void testSuggestTime_nitz_invalidNitzReferenceTimesIgnored() {
130 Scenario scenario = SCENARIO_1;
131 final int systemClockUpdateThreshold = 2000;
132 mScript.pokeFakeClocks(scenario)
133 .pokeThresholds(systemClockUpdateThreshold)
134 .pokeTimeDetectionEnabled(true);
135 TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
136 TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
137
138 // Initialize the strategy / device with a time set from NITZ.
139 mScript.simulateTimePassing(100);
140 long expectedSystemClockMillis1 =
141 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
142 mScript.simulateTimeSignalReceived(timeSignal1)
143 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
144
145 // The UTC time increment should be larger than the system clock update threshold so we
146 // know it shouldn't be ignored for other reasons.
147 long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
148
149 // Now supply a new signal that has an obviously bogus reference time : older than the last
150 // one.
151 long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
152 TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
153 referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
154 TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
155 mScript.simulateTimeSignalReceived(timeSignal2)
156 .verifySystemClockWasNotSetAndResetCallTracking();
157
158 // Now supply a new signal that has an obviously bogus reference time : substantially in the
159 // future.
160 long referenceTimeInFutureMillis =
161 utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
162 TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
163 referenceTimeInFutureMillis, validUtcTimeMillis);
164 TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
165 mScript.simulateTimeSignalReceived(timeSignal3)
166 .verifySystemClockWasNotSetAndResetCallTracking();
167
168 // Just to prove validUtcTimeMillis is valid.
169 long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
170 TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
171 validReferenceTimeMillis, validUtcTimeMillis);
172 long expectedSystemClockMillis4 =
173 TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
174 TimeSignal timeSignal4 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime4);
175 mScript.simulateTimeSignalReceived(timeSignal4)
176 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
177 }
178
179 @Test
180 public void testSuggestTime_timeDetectionToggled() {
181 Scenario scenario = SCENARIO_1;
182 final int clockIncrementMillis = 100;
183 final int systemClockUpdateThreshold = 2000;
184 mScript.pokeFakeClocks(scenario)
185 .pokeThresholds(systemClockUpdateThreshold)
186 .pokeTimeDetectionEnabled(false);
187
188 TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
189 TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
190
191 // Simulate time passing.
192 mScript.simulateTimePassing(clockIncrementMillis);
193
194 // Simulate the time signal being received. It should not be used because auto time
195 // detection is off but it should be recorded.
196 mScript.simulateTimeSignalReceived(timeSignal1)
197 .verifySystemClockWasNotSetAndResetCallTracking();
198
199 // Simulate more time passing.
200 mScript.simulateTimePassing(clockIncrementMillis);
201
202 long expectedSystemClockMillis1 =
203 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
204
205 // Turn on auto time detection.
206 mScript.simulateAutoTimeDetectionToggle()
207 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
208
209 // Turn off auto time detection.
210 mScript.simulateAutoTimeDetectionToggle()
211 .verifySystemClockWasNotSetAndResetCallTracking();
212
213 // Receive another valid time signal.
214 // It should be on the threshold and accounting for the clock increments.
215 TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
216 mScript.peekElapsedRealtimeMillis(),
217 mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
218 TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
219
220 // Simulate more time passing.
221 mScript.simulateTimePassing(clockIncrementMillis);
222
223 long expectedSystemClockMillis2 =
224 TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
225
226 // The new time, though valid, should not be set in the system clock because auto time is
227 // disabled.
228 mScript.simulateTimeSignalReceived(timeSignal2)
229 .verifySystemClockWasNotSetAndResetCallTracking();
230
231 // Turn on auto time detection.
232 mScript.simulateAutoTimeDetectionToggle()
233 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
Neil Fuller4773b9d2018-06-08 18:44:49 +0100234 }
235
236 @Test
237 public void testSuggestTime_unknownSource() {
Neil Fuller4980bbc2018-06-12 21:06:20 +0100238 Scenario scenario = SCENARIO_1;
239 mScript.pokeFakeClocks(scenario)
240 .pokeTimeDetectionEnabled(true);
Neil Fuller4773b9d2018-06-08 18:44:49 +0100241
Neil Fuller4980bbc2018-06-12 21:06:20 +0100242 TimeSignal timeSignal = scenario.createTimeSignalForActual("unknown");
243 mScript.simulateTimeSignalReceived(timeSignal)
244 .verifySystemClockWasNotSetAndResetCallTracking();
Neil Fuller4773b9d2018-06-08 18:44:49 +0100245 }
246
Neil Fuller4980bbc2018-06-12 21:06:20 +0100247 /**
248 * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
249 * like the real thing should, it also asserts preconditions.
250 */
251 private static class FakeCallback implements TimeDetectorStrategy.Callback {
252 private boolean mTimeDetectionEnabled;
253 private boolean mWakeLockAcquired;
254 private long mElapsedRealtimeMillis;
255 private long mSystemClockMillis;
256 private int mSystemClockUpdateThresholdMillis = 2000;
257
258 // Tracking operations.
259 private boolean mSystemClockWasSet;
260 private Intent mBroadcastSent;
261
262 @Override
263 public int systemClockUpdateThresholdMillis() {
264 return mSystemClockUpdateThresholdMillis;
265 }
266
267 @Override
268 public boolean isTimeDetectionEnabled() {
269 return mTimeDetectionEnabled;
270 }
271
272 @Override
273 public void acquireWakeLock() {
274 if (mWakeLockAcquired) {
275 fail("Wake lock already acquired");
276 }
277 mWakeLockAcquired = true;
278 }
279
280 @Override
281 public long elapsedRealtimeMillis() {
282 assertWakeLockAcquired();
283 return mElapsedRealtimeMillis;
284 }
285
286 @Override
287 public long systemClockMillis() {
288 assertWakeLockAcquired();
289 return mSystemClockMillis;
290 }
291
292 @Override
293 public void setSystemClock(long newTimeMillis) {
294 assertWakeLockAcquired();
295 mSystemClockWasSet = true;
296 mSystemClockMillis = newTimeMillis;
297 }
298
299 @Override
300 public void releaseWakeLock() {
301 assertWakeLockAcquired();
302 mWakeLockAcquired = false;
303 }
304
305 @Override
306 public void sendStickyBroadcast(Intent intent) {
307 assertNotNull(intent);
308 mBroadcastSent = intent;
309 }
310
311 // Methods below are for managing the fake's behavior.
312
313 public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
314 mSystemClockUpdateThresholdMillis = thresholdMillis;
315 }
316
317 public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
318 mElapsedRealtimeMillis = elapsedRealtimeMillis;
319 }
320
321 public void pokeSystemClockMillis(long systemClockMillis) {
322 mSystemClockMillis = systemClockMillis;
323 }
324
325 public void pokeTimeDetectionEnabled(boolean enabled) {
326 mTimeDetectionEnabled = enabled;
327 }
328
329 public long peekElapsedRealtimeMillis() {
330 return mElapsedRealtimeMillis;
331 }
332
333 public long peekSystemClockMillis() {
334 return mSystemClockMillis;
335 }
336
337 public void simulateTimePassing(int incrementMillis) {
338 mElapsedRealtimeMillis += incrementMillis;
339 mSystemClockMillis += incrementMillis;
340 }
341
342 public void verifySystemClockNotSet() {
343 assertFalse(mSystemClockWasSet);
344 }
345
346 public void verifySystemClockWasSet(long expectSystemClockMillis) {
347 assertTrue(mSystemClockWasSet);
348 assertEquals(expectSystemClockMillis, mSystemClockMillis);
349 }
350
351 public void verifyIntentWasBroadcast() {
352 assertTrue(mBroadcastSent != null);
353 }
354
355 public void verifyIntentWasNotBroadcast() {
356 assertNull(mBroadcastSent);
357 }
358
359 public void resetCallTracking() {
360 mSystemClockWasSet = false;
361 mBroadcastSent = null;
362 }
363
364 private void assertWakeLockAcquired() {
365 assertTrue("The operation must be performed only after acquiring the wakelock",
366 mWakeLockAcquired);
367 }
368 }
369
370 /**
371 * A fluent helper class for tests.
372 */
373 private class Script {
374
375 private final FakeCallback mFakeCallback;
376 private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
377
378 public Script() {
379 mFakeCallback = new FakeCallback();
380 mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
381 mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
382
383 }
384
385 Script pokeTimeDetectionEnabled(boolean enabled) {
386 mFakeCallback.pokeTimeDetectionEnabled(enabled);
387 return this;
388 }
389
390 Script pokeFakeClocks(Scenario scenario) {
391 mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
392 mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
393 return this;
394 }
395
396 Script pokeThresholds(int systemClockUpdateThreshold) {
397 mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
398 return this;
399 }
400
401 long peekElapsedRealtimeMillis() {
402 return mFakeCallback.peekElapsedRealtimeMillis();
403 }
404
405 long peekSystemClockMillis() {
406 return mFakeCallback.peekSystemClockMillis();
407 }
408
409 Script simulateTimeSignalReceived(TimeSignal timeSignal) {
410 mSimpleTimeDetectorStrategy.suggestTime(timeSignal);
411 return this;
412 }
413
414 Script simulateAutoTimeDetectionToggle() {
415 boolean enabled = !mFakeCallback.isTimeDetectionEnabled();
416 mFakeCallback.pokeTimeDetectionEnabled(enabled);
417 mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled);
418 return this;
419 }
420
421 Script simulateTimePassing(int clockIncrement) {
422 mFakeCallback.simulateTimePassing(clockIncrement);
423 return this;
424 }
425
426 Script verifySystemClockWasNotSetAndResetCallTracking() {
427 mFakeCallback.verifySystemClockNotSet();
428 mFakeCallback.verifyIntentWasNotBroadcast();
429 mFakeCallback.resetCallTracking();
430 return this;
431 }
432
433 Script verifySystemClockWasSetAndResetCallTracking(long expectSystemClockMillis) {
434 mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
435 mFakeCallback.verifyIntentWasBroadcast();
436 mFakeCallback.resetCallTracking();
437 return this;
438 }
439 }
440
441 /**
442 * A starting scenario used during tests. Describes a fictional "physical" reality.
443 */
444 private static class Scenario {
445
446 private final long mInitialDeviceSystemClockMillis;
447 private final long mInitialDeviceRealtimeMillis;
448 private final long mActualTimeMillis;
449
450 Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) {
451 mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
452 mActualTimeMillis = timeMillis;
453 mInitialDeviceRealtimeMillis = elapsedRealtime;
454 }
455
456 long getInitialRealTimeMillis() {
457 return mInitialDeviceRealtimeMillis;
458 }
459
460 long getInitialSystemClockMillis() {
461 return mInitialDeviceSystemClockMillis;
462 }
463
464 long getActualTimeMillis() {
465 return mActualTimeMillis;
466 }
467
468 TimeSignal createTimeSignalForActual(String sourceId) {
469 TimestampedValue<Long> time = new TimestampedValue<>(
470 mInitialDeviceRealtimeMillis, mActualTimeMillis);
471 return new TimeSignal(sourceId, time);
472 }
473
474 static class Builder {
475
476 private long mInitialDeviceSystemClockMillis;
477 private long mInitialDeviceRealtimeMillis;
478 private long mActualTimeMillis;
479
480 Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
481 int hourOfDay, int minute, int second) {
482 mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
483 minute, second);
484 return this;
485 }
486
487 Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
488 mInitialDeviceRealtimeMillis = realtimeMillis;
489 return this;
490 }
491
492 Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
493 int minute, int second) {
494 mActualTimeMillis =
495 createUtcTime(year, monthInYear, day, hourOfDay, minute, second);
496 return this;
497 }
498
499 Scenario build() {
500 return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
501 mActualTimeMillis);
502 }
503 }
504 }
505
506 private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
507 int second) {
508 Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
509 cal.clear();
510 cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
511 return cal.getTimeInMillis();
Neil Fuller4773b9d2018-06-08 18:44:49 +0100512 }
513}