blob: a8572381208d02ff57b6a4ef4a5207568ba316bd [file] [log] [blame]
/*
* Copyright (C) 2018 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.timedetector;
import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.time.ExternalTimeSuggestion;
import android.app.time.ITimeDetectorListener;
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
import android.app.time.TimeState;
import android.app.time.UnixEpochTime;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelableException;
import android.util.NtpTrustedTime;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.timezonedetector.TestCallerIdentityInjector;
import com.android.server.timezonedetector.TestHandler;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.time.Instant;
@RunWith(AndroidJUnit4.class)
public class TimeDetectorServiceTest {
private static final int ARBITRARY_USER_ID = 9999;
private static final int ARBITRARY_SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS = 1234;
private static final Instant ARBITRARY_SUGGESTION_LOWER_BOUND = Instant.ofEpochMilli(0);
private static final Instant ARBITRARY_SUGGESTION_UPPER_BOUND =
Instant.ofEpochMilli(Long.MAX_VALUE);
private static final int[] ARBITRARY_ORIGIN_PRIORITIES = { ORIGIN_NETWORK };
private Context mMockContext;
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeDetectorStrategy mFakeTimeDetectorStrategySpy;
private NtpTrustedTime mMockNtpTrustedTime;
private TimeDetectorService mTimeDetectorService;
@Before
public void setUp() {
mMockContext = mock(Context.class);
// Create a thread + handler for processing the work that the service posts.
mHandlerThread = new HandlerThread("TimeDetectorServiceTest");
mHandlerThread.start();
mTestHandler = new TestHandler(mHandlerThread.getLooper());
mTestCallerIdentityInjector = new TestCallerIdentityInjector();
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeDetectorStrategySpy = spy(new FakeTimeDetectorStrategy());
mMockNtpTrustedTime = mock(NtpTrustedTime.class);
mTimeDetectorService = new TimeDetectorService(
mMockContext, mTestHandler, mTestCallerIdentityInjector,
mFakeServiceConfigAccessorSpy, mFakeTimeDetectorStrategySpy, mMockNtpTrustedTime);
}
@After
public void tearDown() throws Exception {
mHandlerThread.quit();
mHandlerThread.join();
}
@Test
public void testGetCapabilitiesAndConfig_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
assertThrows(SecurityException.class, mTimeDetectorService::getCapabilitiesAndConfig);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testGetCapabilitiesAndConfig() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
TimeCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeDetectorService.getCapabilitiesAndConfig();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
boolean bypassUserPolicyChecks = false;
TimeCapabilitiesAndConfig expectedCapabilitiesAndConfig =
configuration.createCapabilitiesAndConfig(bypassUserPolicyChecks);
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@Test
public void testAddListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
assertThrows(SecurityException.class, () -> mTimeDetectorService.addListener(mockListener));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testRemoveListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
assertThrows(SecurityException.class,
() -> mTimeDetectorService.removeListener(mockListener));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeDetectorListener mockListener = mock(ITimeDetectorListener.class);
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
when(mockListener.asBinder()).thenReturn(mockListenerBinder);
mTimeDetectorService.addListener(mockListener);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).linkToDeath(any(), anyInt());
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
// Simulate the configuration being changed and verify the mockListener was notified.
TimeConfiguration autoDetectEnabledConfiguration =
createTimeConfiguration(true /* autoDetectionEnabled */);
mTimeDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
// The configuration update notification is asynchronous.
mTestHandler.waitForMessagesToBeProcessed();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
verify(mockListener).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
when(mockListener.asBinder()).thenReturn(mockListenerBinder);
when(mockListenerBinder.unlinkToDeath(any(), anyInt())).thenReturn(true);
// Now remove the listener, change the config again, and verify the listener is not
// called.
mTimeDetectorService.removeListener(mockListener);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeConfiguration autoDetectDisabledConfiguration =
createTimeConfiguration(false /* autoDetectionEnabled */);
mTimeDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
verify(mockListener, never()).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
reset(mockListenerBinder, mockListener, mMockContext);
}
}
@Test
public void testSuggestTelephonyTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.suggestTelephonyTime(timeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
}
@Test
public void testSuggestTelephonyTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TelephonyTimeSuggestion timeSuggestion = createTelephonyTimeSuggestion();
mTimeDetectorService.suggestTelephonyTime(timeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE), anyString());
mTestHandler.waitForMessagesToBeProcessed();
verify(mFakeTimeDetectorStrategySpy).suggestTelephonyTime(timeSuggestion);
}
@Test
public void testSuggestManualTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.suggestManualTime(manualTimeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
}
@Test
public void testSuggestManualTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
assertTrue(mTimeDetectorService.suggestManualTime(manualTimeSuggestion));
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
boolean expectedBypassUserPolicyChecks = false;
verify(mFakeTimeDetectorStrategySpy).suggestManualTime(
expectedUserId, manualTimeSuggestion, expectedBypassUserPolicyChecks);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString());
}
@Test
public void testSuggestNetworkTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
NetworkTimeSuggestion networkTimeSuggestion = createNetworkTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
}
@Test
public void testSuggestNetworkTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
NetworkTimeSuggestion networkTimeSuggestion = createNetworkTimeSuggestion();
mTimeDetectorService.suggestNetworkTime(networkTimeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
mTestHandler.waitForMessagesToBeProcessed();
verify(mFakeTimeDetectorStrategySpy).suggestNetworkTime(networkTimeSuggestion);
}
@Test
public void testSuggestGnssTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
}
@Test
public void testSuggestGnssTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
GnssTimeSuggestion gnssTimeSuggestion = createGnssTimeSuggestion();
mTimeDetectorService.suggestGnssTime(gnssTimeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
mTestHandler.waitForMessagesToBeProcessed();
verify(mFakeTimeDetectorStrategySpy).suggestGnssTime(gnssTimeSuggestion);
}
@Test
public void testSuggestExternalTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.suggestExternalTime(externalTimeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
}
@Test
public void testSuggestExternalTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion();
mTimeDetectorService.suggestExternalTime(externalTimeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString());
mTestHandler.waitForMessagesToBeProcessed();
verify(mFakeTimeDetectorStrategySpy).suggestExternalTime(externalTimeSuggestion);
}
@Test
public void testLatestNetworkTime() {
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
UnixEpochTime expected = new UnixEpochTime(
latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
assertEquals(expected, mTimeDetectorService.latestNetworkTime());
}
@Test
public void testLatestNetworkTime_noTimeAvailable() {
when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
}
@Test
public void testGetLatestNetworkSuggestion() {
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
}
@Test
public void testGetLatestNetworkSuggestion_noTimeAvailable() {
when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
}
@Test
public void testGetTimeState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState fakeState = new TimeState(new UnixEpochTime(12345L, 98765L), true);
mFakeTimeDetectorStrategySpy.setTimeState(fakeState);
TimeState actualState = mTimeDetectorService.getTimeState();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
assertEquals(actualState, fakeState);
}
@Test
public void testGetTimeState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
assertThrows(SecurityException.class, mTimeDetectorService::getTimeState);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testSetTimeState() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
mTimeDetectorService.setTimeState(state);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
assertEquals(mFakeTimeDetectorStrategySpy.getTimeState(), state);
}
@Test
public void testSetTimeState_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
TimeState state = new TimeState(new UnixEpochTime(12345L, 98765L), true);
assertThrows(SecurityException.class, () -> mTimeDetectorService.setTimeState(state));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testConfirmTime() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
UnixEpochTime confirmationTime = new UnixEpochTime(12345L, 98765L);
// The fake strategy always returns false.
assertFalse(mTimeDetectorService.confirmTime(confirmationTime));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
verify(mFakeTimeDetectorStrategySpy).confirmTime(confirmationTime);
}
@Test
public void testConfirmTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
assertThrows(SecurityException.class,
() -> mTimeDetectorService.confirmTime(new UnixEpochTime(12345L, 98765L)));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testSetManualTime() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
boolean expectedResult = true; // The test strategy always returns true.
assertEquals(expectedResult,
mTimeDetectorService.setManualTime(timeSuggestion));
// The service calls "suggestManualTime()" because the logic is the same.
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
boolean expectedBypassUserPolicyChecks = false;
verify(mFakeTimeDetectorStrategySpy).suggestManualTime(
expectedUserId, timeSuggestion, expectedBypassUserPolicyChecks);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testSetManualTime_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
ManualTimeSuggestion timeSuggestion = createManualTimeSuggestion();
assertThrows(SecurityException.class,
() -> mTimeDetectorService.setManualTime(timeSuggestion));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
}
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
.thenReturn(PackageManager.PERMISSION_GRANTED);
PrintWriter pw = new PrintWriter(new StringWriter());
mTimeDetectorService.dump(null, pw, null);
verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP));
verify(mFakeTimeDetectorStrategySpy).dump(any(), any());
}
private static TimeConfiguration createTimeConfiguration(boolean autoDetectionEnabled) {
return new TimeConfiguration.Builder()
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
}
static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
.setSystemClockUpdateThresholdMillis(ARBITRARY_SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS)
.setAutoSuggestionLowerBound(ARBITRARY_SUGGESTION_LOWER_BOUND)
.setManualSuggestionLowerBound(ARBITRARY_SUGGESTION_LOWER_BOUND)
.setSuggestionUpperBound(ARBITRARY_SUGGESTION_UPPER_BOUND)
.setOriginPriorities(ARBITRARY_ORIGIN_PRIORITIES)
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.build();
}
private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() {
int slotIndex = 1234;
UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
return new TelephonyTimeSuggestion.Builder(slotIndex)
.setUnixEpochTime(timeValue)
.build();
}
private static ManualTimeSuggestion createManualTimeSuggestion() {
UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
return new ManualTimeSuggestion(timeValue);
}
private static NetworkTimeSuggestion createNetworkTimeSuggestion() {
UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
return new NetworkTimeSuggestion(timeValue, 123);
}
private static GnssTimeSuggestion createGnssTimeSuggestion() {
UnixEpochTime timeValue = new UnixEpochTime(100L, 1_000_000L);
return new GnssTimeSuggestion(timeValue);
}
private static ExternalTimeSuggestion createExternalTimeSuggestion() {
return new ExternalTimeSuggestion(100L, 1_000_000L);
}
}