Merge changes I1476a938,I111189d1

* changes:
  Add shell command to gnss_time_update_service
  Refactoring to ease cherry-pick
This commit is contained in:
Neil Fuller
2022-09-14 15:30:45 +00:00
committed by Gerrit Code Review
3 changed files with 362 additions and 121 deletions

View File

@@ -18,21 +18,26 @@ package com.android.server.timedetector;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.TimeDetector; import android.app.timedetector.TimeDetector;
import android.content.Context; import android.content.Context;
import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.location.LocationManagerInternal; import android.location.LocationManagerInternal;
import android.location.LocationRequest; import android.location.LocationRequest;
import android.location.LocationTime; import android.location.LocationTime;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.TimestampedValue; import android.os.TimestampedValue;
import android.util.LocalLog;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils; import com.android.internal.util.DumpUtils;
import com.android.server.FgThread; import com.android.server.FgThread;
@@ -42,6 +47,8 @@ import com.android.server.SystemService;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.time.Duration; import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Executor;
/** /**
* Monitors the GNSS time. * Monitors the GNSS time.
@@ -66,7 +73,15 @@ public final class GnssTimeUpdateService extends Binder {
@Override @Override
public void onStart() { public void onStart() {
mService = new GnssTimeUpdateService(getContext()); Context context = getContext().createAttributionContext(ATTRIBUTION_TAG);
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
LocationManager locationManager = context.getSystemService(LocationManager.class);
LocationManagerInternal locationManagerInternal =
LocalServices.getService(LocationManagerInternal.class);
TimeDetector timeDetector = context.getSystemService(TimeDetector.class);
mService = new GnssTimeUpdateService(context, alarmManager, locationManager,
locationManagerInternal, timeDetector);
publishBinderService("gnss_time_update_service", mService); publishBinderService("gnss_time_update_service", mService);
} }
@@ -79,7 +94,7 @@ public final class GnssTimeUpdateService extends Binder {
// Instead of polling GNSS time periodically, passive location updates are enabled. // Instead of polling GNSS time periodically, passive location updates are enabled.
// Once an update is received, the gnss time will be queried and suggested to // Once an update is received, the gnss time will be queried and suggested to
// TimeDetectorService. // TimeDetectorService.
mService.requestGnssTimeUpdates(); mService.startGnssListeningInternal();
} }
} }
} }
@@ -87,107 +102,169 @@ public final class GnssTimeUpdateService extends Binder {
private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4); private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4);
private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService"; private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService";
/**
* A log that records the decisions to fetch a GNSS time update.
* This is logged in bug reports to assist with debugging issues with GNSS time suggestions.
*/
private final LocalLog mLocalLog = new LocalLog(10, false /* useLocalTimestamps */);
/** The executor used for async operations */
private final Executor mExecutor = FgThread.getExecutor();
/** The handler used for async operations */
private final Handler mHandler = FgThread.getHandler();
private final Context mContext; private final Context mContext;
private final TimeDetector mTimeDetector; private final TimeDetector mTimeDetector;
private final AlarmManager mAlarmManager; private final AlarmManager mAlarmManager;
private final LocationManager mLocationManager; private final LocationManager mLocationManager;
private final LocationManagerInternal mLocationManagerInternal; private final LocationManagerInternal mLocationManagerInternal;
@Nullable private AlarmManager.OnAlarmListener mAlarmListener;
@Nullable private LocationListener mLocationListener; private final Object mLock = new Object();
@Nullable private TimestampedValue<Long> mLastSuggestedGnssTime; @GuardedBy("mLock") @Nullable private AlarmManager.OnAlarmListener mAlarmListener;
@GuardedBy("mLock") @Nullable private LocationListener mLocationListener;
@Nullable private volatile TimestampedValue<Long> mLastSuggestedGnssTime;
@VisibleForTesting @VisibleForTesting
GnssTimeUpdateService(@NonNull Context context) { GnssTimeUpdateService(@NonNull Context context, @NonNull AlarmManager alarmManager,
mContext = context.createAttributionContext(ATTRIBUTION_TAG); @NonNull LocationManager locationManager,
mTimeDetector = mContext.getSystemService(TimeDetector.class); @NonNull LocationManagerInternal locationManagerInternal,
mLocationManager = mContext.getSystemService(LocationManager.class); @NonNull TimeDetector timeDetector) {
mAlarmManager = mContext.getSystemService(AlarmManager.class); mContext = Objects.requireNonNull(context);
mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class); mAlarmManager = Objects.requireNonNull(alarmManager);
mLocationManager = Objects.requireNonNull(locationManager);
mLocationManagerInternal = Objects.requireNonNull(locationManagerInternal);
mTimeDetector = Objects.requireNonNull(timeDetector);
} }
/** /**
* Request passive location updates. Such a request will not trigger any active locations or * Used by {@link com.android.server.timedetector.GnssTimeUpdateServiceShellCommand} to force
* power usage itself. * the service into GNSS listening mode.
*/ */
@VisibleForTesting @RequiresPermission(android.Manifest.permission.SET_TIME)
void requestGnssTimeUpdates() { boolean startGnssListening() {
if (D) { mContext.enforceCallingPermission(
Log.d(TAG, "requestGnssTimeUpdates()"); android.Manifest.permission.SET_TIME, "Start GNSS listening");
mLocalLog.log("startGnssListening() called");
final long token = Binder.clearCallingIdentity();
try {
return startGnssListeningInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
} }
/**
* Starts listening for passive location updates. Such a request will not trigger any active
* locations or power usage itself. Returns {@code true} if the service is listening after the
* method returns and {@code false} otherwise. At present this method only returns {@code false}
* if there is no GPS provider on the device.
*
* <p>If the service is already listening for locations this is a no-op. If the device is in a
* "sleeping" state between listening periods then it will return to listening.
*/
@VisibleForTesting
boolean startGnssListeningInternal() {
if (!mLocationManager.hasProvider(LocationManager.GPS_PROVIDER)) { if (!mLocationManager.hasProvider(LocationManager.GPS_PROVIDER)) {
Log.e(TAG, "GPS provider does not exist on this device"); logError("GPS provider does not exist on this device");
return; return false;
} }
synchronized (mLock) {
if (mLocationListener != null) {
logDebug("Already listening for GNSS updates");
return true;
}
// If startGnssListening() is called during manual tests to jump back into location
// listening then there will usually be an alarm set.
if (mAlarmListener != null) {
mAlarmManager.cancel(mAlarmListener);
mAlarmListener = null;
}
startGnssListeningLocked();
return true;
}
}
@GuardedBy("mLock")
private void startGnssListeningLocked() {
logDebug("startGnssListeningLocked()");
// Location Listener triggers onLocationChanged() when GNSS data is available, so // Location Listener triggers onLocationChanged() when GNSS data is available, so
// that the getGnssTimeMillis() function doesn't need to be continuously polled. // that the getGnssTimeMillis() function doesn't need to be continuously polled.
mLocationListener = new LocationListener() { mLocationListener = location -> handleLocationAvailable();
@Override
public void onLocationChanged(Location location) {
if (D) {
Log.d(TAG, "onLocationChanged()");
}
// getGnssTimeMillis() can return null when the Master Location Switch for the
// foreground user is disabled.
LocationTime locationTime = mLocationManagerInternal.getGnssTimeMillis();
if (locationTime != null) {
suggestGnssTime(locationTime);
} else {
if (D) {
Log.d(TAG, "getGnssTimeMillis() returned null");
}
}
mLocationManager.removeUpdates(mLocationListener);
mLocationListener = null;
mAlarmListener = new AlarmManager.OnAlarmListener() {
@Override
public void onAlarm() {
if (D) {
Log.d(TAG, "onAlarm()");
}
mAlarmListener = null;
requestGnssTimeUpdates();
}
};
// Set next alarm to re-enable location updates.
long next = SystemClock.elapsedRealtime()
+ GNSS_TIME_UPDATE_ALARM_INTERVAL.toMillis();
mAlarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
next,
TAG,
mAlarmListener,
FgThread.getHandler());
}
};
mLocationManager.requestLocationUpdates( mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL) new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
.setMinUpdateIntervalMillis(0) .setMinUpdateIntervalMillis(0)
.build(), .build(),
FgThread.getExecutor(), mExecutor,
mLocationListener); mLocationListener);
} }
private void handleLocationAvailable() {
logDebug("handleLocationAvailable()");
// getGnssTimeMillis() can return null when the Master Location Switch for the
// foreground user is disabled.
LocationTime locationTime = mLocationManagerInternal.getGnssTimeMillis();
if (locationTime != null) {
String msg = "Passive location time received: " + locationTime;
logDebug(msg);
mLocalLog.log(msg);
suggestGnssTime(locationTime);
} else {
logDebug("getGnssTimeMillis() returned null");
}
synchronized (mLock) {
if (mLocationListener == null) {
logWarning("mLocationListener unexpectedly null");
} else {
mLocationManager.removeUpdates(mLocationListener);
mLocationListener = null;
}
if (mAlarmListener != null) {
logWarning("mAlarmListener was unexpectedly non-null");
mAlarmManager.cancel(mAlarmListener);
}
// Set next alarm to re-enable location updates.
long next = SystemClock.elapsedRealtime()
+ GNSS_TIME_UPDATE_ALARM_INTERVAL.toMillis();
mAlarmListener = this::handleAlarmFired;
mAlarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
next,
TAG,
mAlarmListener,
mHandler);
}
}
private void handleAlarmFired() {
logDebug("handleAlarmFired()");
synchronized (mLock) {
mAlarmListener = null;
startGnssListeningLocked();
}
}
/** /**
* Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector. * Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector.
*/ */
private void suggestGnssTime(LocationTime locationTime) { private void suggestGnssTime(LocationTime locationTime) {
if (D) { logDebug("suggestGnssTime()");
Log.d(TAG, "suggestGnssTime()");
}
long gnssTime = locationTime.getTime(); long gnssTime = locationTime.getTime();
long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L; long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
TimestampedValue<Long> timeSignal = new TimestampedValue<>( TimestampedValue<Long> timeSignal = new TimestampedValue<>(elapsedRealtimeMs, gnssTime);
elapsedRealtimeMs, gnssTime);
mLastSuggestedGnssTime = timeSignal; mLastSuggestedGnssTime = timeSignal;
GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
@@ -198,6 +275,7 @@ public final class GnssTimeUpdateService extends Binder {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime); pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime);
synchronized (mLock) {
pw.print("state: "); pw.print("state: ");
if (mLocationListener != null) { if (mLocationListener != null) {
pw.println("time updates enabled"); pw.println("time updates enabled");
@@ -205,4 +283,30 @@ public final class GnssTimeUpdateService extends Binder {
pw.println("alarm enabled"); pw.println("alarm enabled");
} }
} }
pw.println("Log:");
mLocalLog.dump(pw);
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
new GnssTimeUpdateServiceShellCommand(this).exec(
this, in, out, err, args, callback, resultReceiver);
}
private void logError(String msg) {
Log.e(TAG, msg);
mLocalLog.log(msg);
}
private void logWarning(String msg) {
Log.w(TAG, msg);
mLocalLog.log(msg);
}
private void logDebug(String msg) {
if (D) {
Log.d(TAG, msg);
}
}
} }

View File

@@ -0,0 +1,77 @@
/*
* 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.timedetector;
import android.annotation.NonNull;
import android.os.ShellCommand;
import java.io.PrintWriter;
import java.util.Objects;
/** Implements the shell command interface for {@link GnssTimeUpdateService}. */
class GnssTimeUpdateServiceShellCommand extends ShellCommand {
/**
* The name of the service.
*/
private static final String SHELL_COMMAND_SERVICE_NAME = "gnss_time_update_service";
/**
* A shell command that forces the service in to GNSS listening mode if it isn't already.
*/
private static final String SHELL_COMMAND_START_GNSS_LISTENING = "start_gnss_listening";
@NonNull
private final GnssTimeUpdateService mGnssTimeUpdateService;
GnssTimeUpdateServiceShellCommand(GnssTimeUpdateService gnssTimeUpdateService) {
mGnssTimeUpdateService = Objects.requireNonNull(gnssTimeUpdateService);
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
switch (cmd) {
case SHELL_COMMAND_START_GNSS_LISTENING:
return runStartGnssListening();
default: {
return handleDefaultCommands(cmd);
}
}
}
private int runStartGnssListening() {
boolean success = mGnssTimeUpdateService.startGnssListening();
getOutPrintWriter().println(success);
return 0;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
pw.printf(" help\n");
pw.printf(" Print this help text.\n");
pw.printf(" %s\n", SHELL_COMMAND_START_GNSS_LISTENING);
pw.printf(" Forces the service in to GNSS listening mode (if it isn't already).\n");
pw.printf(" Prints true if the service is listening after this command.\n");
pw.println();
}
}

View File

@@ -16,16 +16,18 @@
package com.android.server.timedetector; package com.android.server.timedetector;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.timedetector.GnssTimeSuggestion; import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.TimeDetector; import android.app.timedetector.TimeDetector;
import android.content.Context; import android.content.Context;
@@ -39,9 +41,6 @@ import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -59,7 +58,7 @@ public final class GnssTimeUpdateServiceTest {
@Mock private TimeDetector mMockTimeDetector; @Mock private TimeDetector mMockTimeDetector;
@Mock private AlarmManager mMockAlarmManager; @Mock private AlarmManager mMockAlarmManager;
@Mock private LocationManager mMockLocationManager; @Mock private LocationManager mMockLocationManager;
@Mock private LocationManagerInternal mLocationManagerInternal; @Mock private LocationManagerInternal mMockLocationManagerInternal;
private GnssTimeUpdateService mGnssTimeUpdateService; private GnssTimeUpdateService mGnssTimeUpdateService;
@@ -67,36 +66,11 @@ public final class GnssTimeUpdateServiceTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mMockContext.createAttributionContext(anyString())) installGpsProviderInMockLocationManager();
.thenReturn(mMockContext);
when(mMockContext.getSystemServiceName(TimeDetector.class)) mGnssTimeUpdateService = new GnssTimeUpdateService(
.thenReturn((TimeDetector.class).getSimpleName()); mMockContext, mMockAlarmManager, mMockLocationManager, mMockLocationManagerInternal,
when(mMockContext.getSystemService(TimeDetector.class)) mMockTimeDetector);
.thenReturn(mMockTimeDetector);
when(mMockContext.getSystemServiceName(LocationManager.class))
.thenReturn((LocationManager.class).getSimpleName());
when(mMockContext.getSystemService(LocationManager.class))
.thenReturn(mMockLocationManager);
when(mMockContext.getSystemServiceName(AlarmManager.class))
.thenReturn((AlarmManager.class).getSimpleName());
when(mMockContext.getSystemService(AlarmManager.class))
.thenReturn(mMockAlarmManager);
when(mMockLocationManager.hasProvider(LocationManager.GPS_PROVIDER))
.thenReturn(true);
LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
mGnssTimeUpdateService =
new GnssTimeUpdateService(mMockContext);
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(LocationManagerInternal.class);
} }
@Test @Test
@@ -105,11 +79,11 @@ public final class GnssTimeUpdateServiceTest {
ELAPSED_REALTIME_MS, GNSS_TIME); ELAPSED_REALTIME_MS, GNSS_TIME);
GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS); LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
doReturn(locationTime).when(mLocationManagerInternal).getGnssTimeMillis(); doReturn(locationTime).when(mMockLocationManagerInternal).getGnssTimeMillis();
mGnssTimeUpdateService.requestGnssTimeUpdates(); assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
ArgumentCaptor<LocationListener> argumentCaptor = ArgumentCaptor<LocationListener> locationListenerCaptor =
ArgumentCaptor.forClass(LocationListener.class); ArgumentCaptor.forClass(LocationListener.class);
verify(mMockLocationManager).requestLocationUpdates( verify(mMockLocationManager).requestLocationUpdates(
eq(LocationManager.GPS_PROVIDER), eq(LocationManager.GPS_PROVIDER),
@@ -117,8 +91,8 @@ public final class GnssTimeUpdateServiceTest {
.setMinUpdateIntervalMillis(0) .setMinUpdateIntervalMillis(0)
.build()), .build()),
any(), any(),
argumentCaptor.capture()); locationListenerCaptor.capture());
LocationListener locationListener = argumentCaptor.getValue(); LocationListener locationListener = locationListenerCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER); Location location = new Location(LocationManager.GPS_PROVIDER);
locationListener.onLocationChanged(location); locationListener.onLocationChanged(location);
@@ -135,11 +109,11 @@ public final class GnssTimeUpdateServiceTest {
@Test @Test
public void testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime() { public void testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime() {
doReturn(null).when(mLocationManagerInternal).getGnssTimeMillis(); doReturn(null).when(mMockLocationManagerInternal).getGnssTimeMillis();
mGnssTimeUpdateService.requestGnssTimeUpdates(); assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
ArgumentCaptor<LocationListener> argumentCaptor = ArgumentCaptor<LocationListener> locationListenerCaptor =
ArgumentCaptor.forClass(LocationListener.class); ArgumentCaptor.forClass(LocationListener.class);
verify(mMockLocationManager).requestLocationUpdates( verify(mMockLocationManager).requestLocationUpdates(
eq(LocationManager.GPS_PROVIDER), eq(LocationManager.GPS_PROVIDER),
@@ -147,14 +121,14 @@ public final class GnssTimeUpdateServiceTest {
.setMinUpdateIntervalMillis(0) .setMinUpdateIntervalMillis(0)
.build()), .build()),
any(), any(),
argumentCaptor.capture()); locationListenerCaptor.capture());
LocationListener locationListener = argumentCaptor.getValue(); LocationListener locationListener = locationListenerCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER); Location location = new Location(LocationManager.GPS_PROVIDER);
locationListener.onLocationChanged(location); locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener); verify(mMockLocationManager).removeUpdates(locationListener);
verify(mMockTimeDetector, never()).suggestGnssTime(any()); verifyZeroInteractions(mMockTimeDetector);
verify(mMockAlarmManager).set( verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(), anyLong(),
@@ -162,4 +136,90 @@ public final class GnssTimeUpdateServiceTest {
any(), any(),
any()); any());
} }
@Test
public void testLocationListeningRestartsAfterSleep() {
ArgumentCaptor<LocationListener> locationListenerCaptor =
ArgumentCaptor.forClass(LocationListener.class);
ArgumentCaptor<OnAlarmListener> alarmListenerCaptor =
ArgumentCaptor.forClass(OnAlarmListener.class);
advanceServiceToSleepingState(locationListenerCaptor, alarmListenerCaptor);
// Simulate the alarm manager's wake-up call.
OnAlarmListener wakeUpListener = alarmListenerCaptor.getValue();
wakeUpListener.onAlarm();
// Verify the service returned to location listening.
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
verifyZeroInteractions(mMockAlarmManager, mMockTimeDetector);
}
// Tests what happens when a call is made to startGnssListeningInternal() when service is
// sleeping. This can happen when the start_gnss_listening shell command is used.
@Test
public void testStartGnssListeningInternalCalledWhenSleeping() {
ArgumentCaptor<LocationListener> locationListenerCaptor =
ArgumentCaptor.forClass(LocationListener.class);
ArgumentCaptor<OnAlarmListener> alarmListenerCaptor =
ArgumentCaptor.forClass(OnAlarmListener.class);
advanceServiceToSleepingState(locationListenerCaptor, alarmListenerCaptor);
// Call startGnssListeningInternal(), as can happen if the start_gnss_listening shell
// command is used.
assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
// Verify the alarm manager is told to stopped sleeping and the location manager is
// listening again.
verify(mMockAlarmManager).cancel(alarmListenerCaptor.getValue());
verify(mMockLocationManager).requestLocationUpdates(any(), any(), any(), any());
verifyZeroInteractions(mMockTimeDetector);
}
private void advanceServiceToSleepingState(
ArgumentCaptor<LocationListener> locationListenerCaptor,
ArgumentCaptor<OnAlarmListener> alarmListenerCaptor) {
TimestampedValue<Long> timeSignal = new TimestampedValue<>(
ELAPSED_REALTIME_MS, GNSS_TIME);
GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
LocationTime locationTime = new LocationTime(GNSS_TIME, ELAPSED_REALTIME_NS);
doReturn(locationTime).when(mMockLocationManagerInternal).getGnssTimeMillis();
assertTrue(mGnssTimeUpdateService.startGnssListeningInternal());
verify(mMockLocationManager).requestLocationUpdates(
any(), any(), any(), locationListenerCaptor.capture());
LocationListener locationListener = locationListenerCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER);
verifyZeroInteractions(mMockAlarmManager, mMockTimeDetector);
locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener);
verify(mMockTimeDetector).suggestGnssTime(timeSuggestion);
// Verify the service is now "sleeping", i.e. waiting for a period before listening for
// GNSS locations again.
verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(),
any(),
alarmListenerCaptor.capture(),
any());
// Reset mocks making it easier to verify the calls that follow.
reset(mMockAlarmManager, mMockTimeDetector, mMockLocationManager,
mMockLocationManagerInternal);
installGpsProviderInMockLocationManager();
}
/**
* Configures the mock response to ensure {@code
* locationManager.hasProvider(LocationManager.GPS_PROVIDER) == true }
*/
private void installGpsProviderInMockLocationManager() {
when(mMockLocationManager.hasProvider(LocationManager.GPS_PROVIDER))
.thenReturn(true);
}
} }