1 /*
2  * Copyright (C) 2021 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.server.timedetector;
18 
19 import static org.junit.Assert.assertTrue;
20 import static org.mockito.ArgumentMatchers.eq;
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.anyLong;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.reset;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.verifyZeroInteractions;
27 import static org.mockito.Mockito.when;
28 
29 import android.app.AlarmManager;
30 import android.app.AlarmManager.OnAlarmListener;
31 import android.app.time.UnixEpochTime;
32 import android.content.Context;
33 import android.location.Location;
34 import android.location.LocationListener;
35 import android.location.LocationManager;
36 import android.location.LocationManagerInternal;
37 import android.location.LocationRequest;
38 import android.location.LocationTime;
39 
40 import androidx.test.runner.AndroidJUnit4;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.ArgumentCaptor;
46 import org.mockito.Mock;
47 import org.mockito.MockitoAnnotations;
48 
49 @RunWith(AndroidJUnit4.class)
50 public final class GnssTimeUpdateServiceTest {
51     private static final long GNSS_TIME = 999_999_999L;
52     private static final long ELAPSED_REALTIME_NS = 123_000_000L;
53     private static final long ELAPSED_REALTIME_MS = ELAPSED_REALTIME_NS / 1_000_000L;
54 
55     @Mock private Context mMockContext;
56     @Mock private AlarmManager mMockAlarmManager;
57     @Mock private LocationManager mMockLocationManager;
58     @Mock private LocationManagerInternal mMockLocationManagerInternal;
59     @Mock private TimeDetectorInternal mMockTimeDetectorInternal;
60 
61     private GnssTimeUpdateService mGnssTimeUpdateService;
62 
63     @Before
setUp()64     public void setUp() {
65         MockitoAnnotations.initMocks(this);
66 
67         installGpsProviderInMockLocationManager();
68 
69         mGnssTimeUpdateService = new GnssTimeUpdateService(
70                 mMockContext, mMockAlarmManager, mMockLocationManager, mMockLocationManagerInternal,
71                 mMockTimeDetectorInternal);
72     }
73 
74     @Test
testLocationListenerOnLocationChanged_validLocationTime_suggestsGnssTime()75     public void testLocationListenerOnLocationChanged_validLocationTime_suggestsGnssTime() {
76         UnixEpochTime timeSignal = new UnixEpochTime(
77                 ELAPSED_REALTIME_MS, GNSS_TIME);
78         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
79         LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
80         doReturn(locationTime).when(mMockLocationManagerInternal).getGnssTimeMillis();
81 
82         assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
83 
84         ArgumentCaptor<LocationListener> locationListenerCaptor =
85                 ArgumentCaptor.forClass(LocationListener.class);
86         verify(mMockLocationManager).requestLocationUpdates(
87                 eq(LocationManager.GPS_PROVIDER),
88                 eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
89                     .setMinUpdateIntervalMillis(0)
90                     .build()),
91                 any(),
92                 locationListenerCaptor.capture());
93         LocationListener locationListener = locationListenerCaptor.getValue();
94         Location location = new Location(LocationManager.GPS_PROVIDER);
95 
96         locationListener.onLocationChanged(location);
97 
98         verify(mMockLocationManager).removeUpdates(locationListener);
99         verify(mMockTimeDetectorInternal).suggestGnssTime(timeSuggestion);
100         verify(mMockAlarmManager).set(
101                 eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
102                 anyLong(),
103                 any(),
104                 any(),
105                 any());
106     }
107 
108     @Test
testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime()109     public void testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime() {
110         doReturn(null).when(mMockLocationManagerInternal).getGnssTimeMillis();
111 
112         assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
113 
114         ArgumentCaptor<LocationListener> locationListenerCaptor =
115                 ArgumentCaptor.forClass(LocationListener.class);
116         verify(mMockLocationManager).requestLocationUpdates(
117                 eq(LocationManager.GPS_PROVIDER),
118                 eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
119                     .setMinUpdateIntervalMillis(0)
120                     .build()),
121                 any(),
122                 locationListenerCaptor.capture());
123         LocationListener locationListener = locationListenerCaptor.getValue();
124         Location location = new Location(LocationManager.GPS_PROVIDER);
125 
126         locationListener.onLocationChanged(location);
127 
128         verify(mMockLocationManager).removeUpdates(locationListener);
129         verifyZeroInteractions(mMockTimeDetectorInternal);
130         verify(mMockAlarmManager).set(
131                 eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
132                 anyLong(),
133                 any(),
134                 any(),
135                 any());
136     }
137 
138     @Test
testLocationListeningRestartsAfterSleep()139     public void testLocationListeningRestartsAfterSleep() {
140         ArgumentCaptor<LocationListener> locationListenerCaptor =
141                 ArgumentCaptor.forClass(LocationListener.class);
142         ArgumentCaptor<OnAlarmListener> alarmListenerCaptor =
143                 ArgumentCaptor.forClass(OnAlarmListener.class);
144 
145         advanceServiceToSleepingState(locationListenerCaptor, alarmListenerCaptor);
146 
147         // Simulate the alarm manager's wake-up call.
148         OnAlarmListener wakeUpListener = alarmListenerCaptor.getValue();
149         wakeUpListener.onAlarm();
150 
151         // Verify the service returned to location listening.
152         verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
153         verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
154     }
155 
156     // Tests what happens when a call is made to startGnssListeningInternal() when service is
157     // sleeping. This can happen when the start_gnss_listening shell command is used.
158     @Test
testStartGnssListeningInternalCalledWhenSleeping()159     public void testStartGnssListeningInternalCalledWhenSleeping() {
160         ArgumentCaptor<LocationListener> locationListenerCaptor =
161                 ArgumentCaptor.forClass(LocationListener.class);
162         ArgumentCaptor<OnAlarmListener> alarmListenerCaptor =
163                 ArgumentCaptor.forClass(OnAlarmListener.class);
164 
165         advanceServiceToSleepingState(locationListenerCaptor, alarmListenerCaptor);
166 
167         // Call startGnssListeningInternal(), as can happen if the start_gnss_listening shell
168         // command is used.
169         assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
170 
171         // Verify the alarm manager is told to stopped sleeping and the location manager is
172         // listening again.
173         verify(mMockAlarmManager).cancel(alarmListenerCaptor.getValue());
174         verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
175         verifyZeroInteractions(mMockTimeDetectorInternal);
176     }
177 
advanceServiceToSleepingState( ArgumentCaptor<LocationListener> locationListenerCaptor, ArgumentCaptor<OnAlarmListener> alarmListenerCaptor)178     private void advanceServiceToSleepingState(
179             ArgumentCaptor<LocationListener> locationListenerCaptor,
180             ArgumentCaptor<OnAlarmListener> alarmListenerCaptor) {
181         UnixEpochTime timeSignal = new UnixEpochTime(
182                 ELAPSED_REALTIME_MS, GNSS_TIME);
183         GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
184         LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
185         doReturn(locationTime).when(mMockLocationManagerInternal).getGnssTimeMillis();
186 
187         assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
188 
189         verify(mMockLocationManager).requestLocationUpdates(
190                 any(), any(), any(), locationListenerCaptor.capture());
191         LocationListener locationListener = locationListenerCaptor.getValue();
192         Location location = new Location(LocationManager.GPS_PROVIDER);
193         verifyZeroInteractions(mMockAlarmManager, mMockTimeDetectorInternal);
194 
195         locationListener.onLocationChanged(location);
196 
197         verify(mMockLocationManager).removeUpdates(locationListener);
198         verify(mMockTimeDetectorInternal).suggestGnssTime(timeSuggestion);
199 
200         // Verify the service is now "sleeping", i.e. waiting for a period before listening for
201         // GNSS locations again.
202         verify(mMockAlarmManager).set(
203                 eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
204                 anyLong(),
205                 any(),
206                 alarmListenerCaptor.capture(),
207                 any());
208 
209         // Reset mocks making it easier to verify the calls that follow.
210         reset(mMockAlarmManager, mMockTimeDetectorInternal, mMockLocationManager,
211                 mMockLocationManagerInternal);
212         installGpsProviderInMockLocationManager();
213     }
214 
215     /**
216      * Configures the mock response to ensure {@code
217      * locationManager.hasProvider(LocationManager.GPS_PROVIDER) == true }
218      */
installGpsProviderInMockLocationManager()219     private void installGpsProviderInMockLocationManager() {
220         when(mMockLocationManager.hasProvider(LocationManager.GPS_PROVIDER))
221                 .thenReturn(true);
222     }
223 }
224