blob: 3e2e46c520c00afe6f16402fba25abcf2af2c993 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.location.gnss;
import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.MAX_NETWORK_TIME_AGE_MILLIS;
import static com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.NTP_REFRESH_INTERVAL_MILLIS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.time.UnixEpochTime;
import android.platform.test.annotations.Presubmit;
import com.android.server.location.gnss.NetworkTimeHelper.InjectTimeCallback;
import com.android.server.location.gnss.TimeDetectorNetworkTimeHelper.Environment;
import com.android.server.timedetector.NetworkTimeSuggestion;
import com.android.server.timezonedetector.StateChangeListener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link TimeDetectorNetworkTimeHelper}.
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
public class TimeDetectorNetworkTimeHelperTest {
private static final NetworkTimeSuggestion ARBITRARY_NETWORK_TIME =
new NetworkTimeSuggestion(new UnixEpochTime(1234L, 7777L), 123);
private FakeEnvironment mFakeEnvironment;
@Mock private InjectTimeCallback mMockInjectTimeCallback;
private TimeDetectorNetworkTimeHelper mTimeDetectorNetworkTimeHelper;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFakeEnvironment = new FakeEnvironment();
mTimeDetectorNetworkTimeHelper = new TimeDetectorNetworkTimeHelper(
mFakeEnvironment, mMockInjectTimeCallback);
// TimeDetectorNetworkTimeHelper should register for network time updates during
// construction.
mFakeEnvironment.assertHasNetworkTimeChangeListener();
}
@Test
public void setPeriodicTimeInjectionMode_true() {
testSetPeriodicTimeInjectionMode(true);
}
@Test
public void setPeriodicTimeInjectionMode_false() {
testSetPeriodicTimeInjectionMode(false);
}
private void testSetPeriodicTimeInjectionMode(boolean periodicTimeInjectionMode) {
NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
int millisElapsedSinceNetworkTimeReceived = 1000;
mFakeEnvironment.pokeLatestNetworkTime(networkTime);
long currentElapsedRealtimeMillis =
networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ millisElapsedSinceNetworkTimeReceived;
mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(periodicTimeInjectionMode);
// All injections are async, so we have to simulate the async work taking place.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
// Any call to setPeriodicTimeInjectionMode() should result in an (async) injected time
verify(mMockInjectTimeCallback).injectTime(
networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
networkTime.getUncertaintyMillis());
// Check whether the scheduled async is set up / not set up for the periodic request.
if (periodicTimeInjectionMode) {
mFakeEnvironment.assertHasScheduledAsyncCallback(
mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
} else {
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
}
}
@Test
public void periodicInjectionBehavior() {
// Set the elapsed realtime clock to an arbitrary start value.
mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
// Configure periodic time injections. Doing so should cause a time query, but no time is
// available.
mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
// All query/injections are async, so we have to simulate the async work taking place.
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
// No time available, so no injection.
verifyNoMoreInteractions(mMockInjectTimeCallback);
// A periodic check should be scheduled.
mFakeEnvironment.assertHasScheduledAsyncCallback(
mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
// Time passes...
mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
// A network time becomes available: This should cause the registered listener to trigger.
NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
// All query/injections are async, so we have to simulate the async work taking place,
// causing a query, time injection and a re-schedule.
mFakeEnvironment.simulateTimeAdvancing(1);
verify(mMockInjectTimeCallback).injectTime(
networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
networkTime.getUncertaintyMillis());
// A new periodic check should be scheduled.
mFakeEnvironment.assertHasNoImmediateCallback();
mFakeEnvironment.assertHasScheduledAsyncCallback(
mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
int arbitraryIterationCount = 3;
for (int i = 0; i < arbitraryIterationCount; i++) {
// Advance by the amount needed for the scheduled work to run. That work should query
// and inject.
mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS);
// All query/injections are async, so we have to simulate the async work taking place,
// causing a query, time injection and a re-schedule.
verify(mMockInjectTimeCallback).injectTime(
networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
networkTime.getUncertaintyMillis());
// A new periodic check should be scheduled.
mFakeEnvironment.assertHasScheduledAsyncCallback(
mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
mFakeEnvironment.assertHasNoImmediateCallback();
}
}
@Test
public void networkTimeAvailableBehavior() {
// Set the elapsed realtime clock to an arbitrary start value.
mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
// No periodic time injections. This call causes a time query, but no time is available yet.
mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
// All query/injections are async, so we have to simulate the async work taking place.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
// No time available, so no injection.
verifyNoMoreInteractions(mMockInjectTimeCallback);
// No periodic check should be scheduled.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
// Time passes...
mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
// A network time becomes available: This should cause the registered listener to trigger
// and cause time to be injected.
NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
mFakeEnvironment.simulateLatestNetworkTimeChange(networkTime);
// All query/injections are async, so we have to simulate the async work taking place,
// causing a query, time injection and a re-schedule.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
verify(mMockInjectTimeCallback).injectTime(
networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
networkTime.getUncertaintyMillis());
// No periodic check should be scheduled.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasNoImmediateCallback();
}
@Test
public void networkConnectivityAvailableBehavior() {
// Set the elapsed realtime clock to an arbitrary start value.
mFakeEnvironment.pokeElapsedRealtimeMillis(12345L);
// No periodic time injections. This call causes a time query, but no time is available yet.
mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(false);
// All query/injections are async, so we have to simulate the async work taking place.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
// No time available, so no injection.
verifyNoMoreInteractions(mMockInjectTimeCallback);
// No periodic check should be scheduled.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
// Time passes...
mFakeEnvironment.simulateTimeAdvancing(NTP_REFRESH_INTERVAL_MILLIS / 2);
NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
mFakeEnvironment.pokeLatestNetworkTime(networkTime);
// Simulate location code noticing that connectivity has changed and notifying the helper.
mTimeDetectorNetworkTimeHelper.onNetworkAvailable();
// All query/injections are async, so we have to simulate the async work taking place,
// causing a query, time injection and a re-schedule.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
mFakeEnvironment.simulateTimeAdvancing(1);
verify(mMockInjectTimeCallback).injectTime(
networkTime.getUnixEpochTime().getUnixEpochTimeMillis(),
networkTime.getUnixEpochTime().getElapsedRealtimeMillis(),
networkTime.getUncertaintyMillis());
// No periodic check should be scheduled.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasNoImmediateCallback();
}
@Test
public void oldTimesNotInjected() {
NetworkTimeSuggestion networkTime = ARBITRARY_NETWORK_TIME;
mFakeEnvironment.pokeLatestNetworkTime(networkTime);
int millisElapsedSinceNetworkTimeReceived = MAX_NETWORK_TIME_AGE_MILLIS;
long currentElapsedRealtimeMillis =
networkTime.getUnixEpochTime().getElapsedRealtimeMillis()
+ millisElapsedSinceNetworkTimeReceived;
mFakeEnvironment.pokeElapsedRealtimeMillis(currentElapsedRealtimeMillis);
mTimeDetectorNetworkTimeHelper.setPeriodicTimeInjectionMode(true);
// All injections are async, so we have to simulate the async work taking place.
mFakeEnvironment.assertHasNoScheduledAsyncCallback();
mFakeEnvironment.assertHasImmediateCallback();
// The age of the network time will now be MAX_NETWORK_TIME_AGE_MILLIS + 1, which is too
// old to inject.
mFakeEnvironment.simulateTimeAdvancing(1);
// Old network times should not be injected.
verify(mMockInjectTimeCallback, never()).injectTime(anyLong(), anyLong(), anyInt());
// Check whether the scheduled async is set up / not set up for the periodic request.
mFakeEnvironment.assertHasScheduledAsyncCallback(
mFakeEnvironment.elapsedRealtimeMillis() + NTP_REFRESH_INTERVAL_MILLIS);
}
/** A fake implementation of {@link Environment} for use by this test. */
private static class FakeEnvironment implements Environment {
private StateChangeListener mNetworkTimeUpdateListener;
private long mCurrentElapsedRealtimeMillis;
private NetworkTimeSuggestion mLatestNetworkTime;
private TimeDetectorNetworkTimeHelper mImmediateAsyncCallback;
private String mImmediateAsyncCallbackReason;
private TimeDetectorNetworkTimeHelper mScheduledAsyncCallback;
private long mScheduledAsyncRunnableTimeMillis;
@Override
public long elapsedRealtimeMillis() {
return mCurrentElapsedRealtimeMillis;
}
@Override
public NetworkTimeSuggestion getLatestNetworkTime() {
return mLatestNetworkTime;
}
@Override
public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) {
mNetworkTimeUpdateListener = stateChangeListener;
}
@Override
public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper,
String reason) {
if (mImmediateAsyncCallback != null) {
fail("Only one immediate callback expected at a time, found reason: "
+ mImmediateAsyncCallbackReason);
}
mImmediateAsyncCallback = helper;
mImmediateAsyncCallbackReason = reason;
}
@Override
public void requestDelayedTimeQueryCallback(
TimeDetectorNetworkTimeHelper instance, long delayMillis) {
mScheduledAsyncCallback = instance;
mScheduledAsyncRunnableTimeMillis = mCurrentElapsedRealtimeMillis + delayMillis;
}
@Override
public void clearDelayedTimeQueryCallback() {
mScheduledAsyncCallback = null;
mScheduledAsyncRunnableTimeMillis = -1;
}
void pokeLatestNetworkTime(NetworkTimeSuggestion networkTime) {
mLatestNetworkTime = networkTime;
}
void pokeElapsedRealtimeMillis(long currentElapsedRealtimeMillis) {
mCurrentElapsedRealtimeMillis = currentElapsedRealtimeMillis;
}
void simulateLatestNetworkTimeChange(NetworkTimeSuggestion networkTime) {
mLatestNetworkTime = networkTime;
mNetworkTimeUpdateListener.onChange();
}
void simulateTimeAdvancing(long durationMillis) {
mCurrentElapsedRealtimeMillis += durationMillis;
if (mImmediateAsyncCallback != null) {
TimeDetectorNetworkTimeHelper helper = mImmediateAsyncCallback;
String reason = mImmediateAsyncCallbackReason;
mImmediateAsyncCallback = null;
mImmediateAsyncCallbackReason = null;
helper.queryAndInjectNetworkTime(reason);
}
if (mScheduledAsyncCallback != null
&& mCurrentElapsedRealtimeMillis >= mScheduledAsyncRunnableTimeMillis) {
TimeDetectorNetworkTimeHelper helper = mScheduledAsyncCallback;
mScheduledAsyncCallback = null;
mScheduledAsyncRunnableTimeMillis = -1;
helper.delayedQueryAndInjectNetworkTime();
}
}
void assertHasNetworkTimeChangeListener() {
assertNotNull(mNetworkTimeUpdateListener);
}
void assertHasImmediateCallback() {
assertNotNull(mImmediateAsyncCallback);
}
void assertHasNoImmediateCallback() {
assertNull(mImmediateAsyncCallback);
}
void assertHasScheduledAsyncCallback(long expectedScheduledAsyncRunnableTimeMillis) {
assertNotNull(mScheduledAsyncCallback);
assertEquals(expectedScheduledAsyncRunnableTimeMillis,
mScheduledAsyncRunnableTimeMillis);
}
void assertHasNoScheduledAsyncCallback() {
assertNull(mScheduledAsyncCallback);
}
}
}