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:
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user