Add GnssTimeUpdateService.

GnssTimeUpdateService listens for location updates. Once an update is
received, it reads the time provided by GPS and suggests it to
TimeDetectorService. TimeDetectorService can then use it to set the
SystemClock based on included time sources and priority.

Bug: 157265008
Test: Presubmits + atest + Manual
- atest frameworks/base/services/tests/servicestests/src/com/android/server/timedetector/GnssTimeUpdateServiceTest.java
- For manual testing, the Location Shell Commands can be used to add /
  enable a gps test provider to set gps locations with a specified time.
  The time detector dumpsys can then be used to verify that a GNSS
  suggestion has been made.

Change-Id: Ife27ecd6d122f33b7cc93d7832e3085acc33c160
This commit is contained in:
Eva Chen
2021-01-11 11:28:02 -08:00
parent a10381aa7a
commit 270ae737c2
5 changed files with 382 additions and 0 deletions

View File

@@ -1570,6 +1570,10 @@
<item>network</item>
</string-array>
<!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. -->
<bool name="config_enableGnssTimeUpdateService">false</bool>
<!-- Enables the TimeZoneRuleManager service. This is the global switch for the updateable time
zone update mechanism. -->
<bool name="config_enableUpdateableTimeZoneRules">false</bool>

View File

@@ -2175,6 +2175,7 @@
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
<java-symbol type="array" name="config_autoTimeSourcesPriority" />
<java-symbol type="bool" name="config_enableGnssTimeUpdateService" />
<java-symbol type="bool" name="config_enableGeolocationTimeZoneDetection" />
<java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneProvider" />
<java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneOverlay" />

View File

@@ -0,0 +1,203 @@
/*
* Copyright (C) 2021 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.annotation.Nullable;
import android.app.AlarmManager;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.TimeDetector;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
import android.location.LocationTime;
import android.os.Binder;
import android.os.SystemClock;
import android.os.TimestampedValue;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
/**
* Monitors the GNSS time.
*
* <p>When available, the time is always suggested to the {@link
* com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
* system clock, depending on user settings and what other signals are available.
*/
public final class GnssTimeUpdateService extends Binder {
private static final String TAG = "GnssTimeUpdateService";
private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
/**
* Handles the lifecycle events for the GnssTimeUpdateService.
*/
public static class Lifecycle extends SystemService {
private GnssTimeUpdateService mService;
public Lifecycle(@NonNull Context context) {
super(context);
}
@Override
public void onStart() {
mService = new GnssTimeUpdateService(getContext());
publishBinderService("gnss_time_update_service", mService);
}
@Override
public void onBootPhase(int phase) {
// Need to wait for some location providers to be enabled. If done at
// PHASE_SYSTEM_SERVICES_READY, error where "gps" provider does not exist could occur.
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
// Initiate location updates. On boot, GNSS might not be available right away.
// 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
// TimeDetectorService.
mService.requestGnssTimeUpdates();
}
}
}
private static final Duration GNSS_TIME_UPDATE_ALARM_INTERVAL = Duration.ofHours(4);
private static final String ATTRIBUTION_TAG = "GnssTimeUpdateService";
private final Context mContext;
private final TimeDetector mTimeDetector;
private final AlarmManager mAlarmManager;
private final LocationManager mLocationManager;
private final LocationManagerInternal mLocationManagerInternal;
@Nullable private AlarmManager.OnAlarmListener mAlarmListener;
@Nullable private LocationListener mLocationListener;
@Nullable private TimestampedValue<Long> mLastSuggestedGnssTime;
@VisibleForTesting
GnssTimeUpdateService(@NonNull Context context) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mTimeDetector = mContext.getSystemService(TimeDetector.class);
mLocationManager = mContext.getSystemService(LocationManager.class);
mAlarmManager = mContext.getSystemService(AlarmManager.class);
mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class);
}
/**
* Request passive location updates. Such a request will not trigger any active locations or
* power usage itself.
*/
@VisibleForTesting
void requestGnssTimeUpdates() {
if (D) {
Log.d(TAG, "requestGnssTimeUpdates()");
}
// Location Listener triggers onLocationChanged() when GNSS data is available, so
// that the getGnssTimeMillis() function doesn't need to be continuously polled.
mLocationListener = new LocationListener() {
@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(
LocationManager.GPS_PROVIDER,
new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
.setMinUpdateIntervalMillis(0)
.build(),
FgThread.getExecutor(),
mLocationListener);
}
/**
* Convert LocationTime to TimestampedValue. Then suggest TimestampedValue to Time Detector.
*/
private void suggestGnssTime(LocationTime locationTime) {
if (D) {
Log.d(TAG, "suggestGnssTime()");
}
long gnssTime = locationTime.getTime();
long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L;
TimestampedValue<Long> timeSignal = new TimestampedValue<>(
elapsedRealtimeMs, gnssTime);
mLastSuggestedGnssTime = timeSignal;
GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal);
mTimeDetector.suggestGnssTime(timeSuggestion);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
pw.println("mLastSuggestedGnssTime: " + mLastSuggestedGnssTime);
pw.print("state: ");
if (mLocationListener != null) {
pw.println("time updates enabled");
} else {
pw.println("alarm enabled");
}
}
}

View File

@@ -323,6 +323,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
"com.android.server.location.timezone.LocationTimeZoneManagerService$Lifecycle";
private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
"com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
private static final String ADB_SERVICE_CLASS =
@@ -1874,6 +1876,16 @@ public final class SystemServer implements Dumpable {
}
t.traceEnd();
if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) {
t.traceBegin("StartGnssTimeUpdateService");
try {
mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS);
} catch (Throwable e) {
reportWtf("starting GnssTimeUpdateService service", e);
}
t.traceEnd();
}
if (!isWatch) {
t.traceBegin("StartSearchManagerService");
try {

View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 2021 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 org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.TimeDetector;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
import android.location.LocationTime;
import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public final class GnssTimeUpdateServiceTest {
private static final long GNSS_TIME = 999_999_999L;
private static final long ELAPSED_REALTIME_NS = 123_000_000L;
private static final long ELAPSED_REALTIME_MS = ELAPSED_REALTIME_NS / 1_000_000L;
@Mock private Context mMockContext;
@Mock private TimeDetector mMockTimeDetector;
@Mock private AlarmManager mMockAlarmManager;
@Mock private LocationManager mMockLocationManager;
@Mock private LocationManagerInternal mLocationManagerInternal;
private GnssTimeUpdateService mGnssTimeUpdateService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockContext.createAttributionContext(anyString()))
.thenReturn(mMockContext);
when(mMockContext.getSystemServiceName(TimeDetector.class))
.thenReturn((TimeDetector.class).getSimpleName());
when(mMockContext.getSystemService(TimeDetector.class))
.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);
LocalServices.addService(LocationManagerInternal.class, mLocationManagerInternal);
mGnssTimeUpdateService =
new GnssTimeUpdateService(mMockContext);
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(LocationManagerInternal.class);
}
@Test
public void testLocationListenerOnLocationChanged_validLocationTime_suggestsGnssTime() {
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(mLocationManagerInternal).getGnssTimeMillis();
mGnssTimeUpdateService.requestGnssTimeUpdates();
ArgumentCaptor<LocationListener> argumentCaptor =
ArgumentCaptor.forClass(LocationListener.class);
verify(mMockLocationManager).requestLocationUpdates(
eq(LocationManager.GPS_PROVIDER),
eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
.setMinUpdateIntervalMillis(0)
.build()),
any(),
argumentCaptor.capture());
LocationListener locationListener = argumentCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER);
locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener);
verify(mMockTimeDetector).suggestGnssTime(timeSuggestion);
verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(),
any(),
any(),
any());
}
@Test
public void testLocationListenerOnLocationChanged_nullLocationTime_doesNotSuggestGnssTime() {
doReturn(null).when(mLocationManagerInternal).getGnssTimeMillis();
mGnssTimeUpdateService.requestGnssTimeUpdates();
ArgumentCaptor<LocationListener> argumentCaptor =
ArgumentCaptor.forClass(LocationListener.class);
verify(mMockLocationManager).requestLocationUpdates(
eq(LocationManager.GPS_PROVIDER),
eq(new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
.setMinUpdateIntervalMillis(0)
.build()),
any(),
argumentCaptor.capture());
LocationListener locationListener = argumentCaptor.getValue();
Location location = new Location(LocationManager.GPS_PROVIDER);
locationListener.onLocationChanged(location);
verify(mMockLocationManager).removeUpdates(locationListener);
verify(mMockTimeDetector, never()).suggestGnssTime(any());
verify(mMockAlarmManager).set(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
anyLong(),
any(),
any(),
any());
}
}