Move network time logic out of AlarmManagerService

Move network time logic out of AlarmManagerService in preparation for
a future refactoring. The TimeDetectorService is now a better home for
functionality associated with time signals, as it should receive most
time signals from various sources so it can determine whether the
device's system clock needs to change.

Although the TimeDetectorService is not currently informed every time
the NtpTrustedTime obtains a new time, it should be in future after
further work in later commits.

This change modifies the logic in SystemClock to match the GNSS time
logic in the same class, i.e. rather than calculating the "current time"
in the system server and then sending it over binder (introducing a
delay), the latest information needed to compute the time is sent to the
client, which then calculates the current time.

Bug: 219693030
Bug: 222295093
Test: atest services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
Test: atest cts/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
Test: Treehugger
Change-Id: I80ea52877352122b52ac54daed8b092f3cc18e02
This commit is contained in:
Neil Fuller
2022-06-27 15:36:58 +01:00
parent 2ddcf674a4
commit 83ad73ecdc
9 changed files with 227 additions and 46 deletions

View File

@@ -40,7 +40,6 @@ interface IAlarmManager {
long getNextWakeFromIdleTime();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
long currentNetworkTimeMillis();
boolean canScheduleExactAlarms(String packageName);
boolean hasScheduleExactAlarm(String packageName, int userId);
int getConfigVersion();

View File

@@ -90,7 +90,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelableException;
import android.os.PowerExemptionManager;
import android.os.PowerManager;
import android.os.Process;
@@ -116,7 +115,6 @@ import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongArrayQueue;
import android.util.NtpTrustedTime;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -161,7 +159,6 @@ import libcore.util.EmptyArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -3023,17 +3020,6 @@ public class AlarmManagerService extends SystemService {
return getNextAlarmClockImpl(userId);
}
@Override
public long currentNetworkTimeMillis() {
final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
if (ntpResult != null) {
return ntpResult.currentTimeMillis();
} else {
throw new ParcelableException(new DateTimeException("Missing NTP fix"));
}
}
@Override
public int getConfigVersion() {
getContext().enforceCallingOrSelfPermission(Manifest.permission.DUMP,

View File

@@ -24,6 +24,7 @@ import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
/**
* System private API to communicate with time detector service.
@@ -45,9 +46,11 @@ interface ITimeDetectorService {
boolean updateConfiguration(in TimeConfiguration timeConfiguration);
void suggestExternalTime( in ExternalTimeSuggestion timeSuggestion);
void suggestExternalTime(in ExternalTimeSuggestion timeSuggestion);
void suggestGnssTime(in GnssTimeSuggestion timeSuggestion);
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion);
void suggestTelephonyTime(in TelephonyTimeSuggestion timeSuggestion);
TimePoint latestNetworkTime();
}

View File

@@ -0,0 +1,19 @@
/*
* 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 android.app.timedetector;
parcelable TimePoint;

View File

@@ -0,0 +1,103 @@
/*
* 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 android.app.timedetector;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Data class for passing a Unix epoch time anchored to the elapsed realtime clock.
*
* @hide
*/
public final class TimePoint implements Parcelable {
private final long mUnixEpochTimeMillis;
private final long mElapsedRealtimeMillis;
public TimePoint(long unixEpochTimeMillis, long elapsedRealtimeMillis) {
mUnixEpochTimeMillis = unixEpochTimeMillis;
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
/**
* The current Unix epoch time, according to the external source.
*/
public long getUnixEpochTimeMillis() {
return mUnixEpochTimeMillis;
}
/**
* The elapsed millis since boot when {@link #getUnixEpochTimeMillis} was computed.
*/
public long getElapsedRealtimeMillis() {
return mElapsedRealtimeMillis;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mUnixEpochTimeMillis);
out.writeLong(mElapsedRealtimeMillis);
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TimePoint)) {
return false;
}
TimePoint timePoint = (TimePoint) o;
return mUnixEpochTimeMillis == timePoint.mUnixEpochTimeMillis
&& mElapsedRealtimeMillis == timePoint.mElapsedRealtimeMillis;
}
@Override
public int hashCode() {
return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis);
}
@Override
public String toString() {
return "TimePoint{"
+ "mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+ ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+ '}';
}
public static final @NonNull Creator<TimePoint> CREATOR =
new Creator<TimePoint>() {
public TimePoint createFromParcel(Parcel in) {
long unixEpochTime = in.readLong();
long elapsedRealtimeMillis = in.readLong();
return new TimePoint(unixEpochTime, elapsedRealtimeMillis);
}
public TimePoint[] newArray(int size) {
return new TimePoint[size];
}
};
}

View File

@@ -18,6 +18,8 @@ package android.os;
import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.TimePoint;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.location.ILocationManager;
@@ -170,6 +172,14 @@ public final class SystemClock {
return false;
}
private static IAlarmManager getIAlarmManager() {
if (sIAlarmManager == null) {
sIAlarmManager = IAlarmManager.Stub
.asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
}
return sIAlarmManager;
}
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
*
@@ -269,56 +279,71 @@ public final class SystemClock {
* <p>
* While the time returned by {@link System#currentTimeMillis()} can be
* adjusted by the user, the time returned by this method cannot be adjusted
* by the user. Note that synchronization may occur using an insecure
* network protocol, so the returned time should not be used for security
* purposes.
* by the user.
* <p>
* This performs no blocking network operations and returns values based on
* a recent successful synchronization event; it will either return a valid
* time or throw.
* <p>
* Note that synchronization may occur using an insecure network protocol,
* so the returned time should not be used for security purposes.
* The device may resynchronize with the same or different network source
* at any time. Due to network delays, variations between servers, or local
* (client side) clock drift, the accuracy of the returned times cannot be
* guaranteed. In extreme cases, consecutive calls to {@link
* #currentNetworkTimeMillis()} could return times that are out of order.
*
* @throws DateTimeException when no accurate network time can be provided.
* @throws DateTimeException when no network time can be provided.
* @hide
*/
public static long currentNetworkTimeMillis() {
final IAlarmManager mgr = getIAlarmManager();
if (mgr != null) {
ITimeDetectorService timeDetectorService = ITimeDetectorService.Stub
.asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE));
if (timeDetectorService != null) {
TimePoint time;
try {
return mgr.currentNetworkTimeMillis();
time = timeDetectorService.latestNetworkTime();
} catch (ParcelableException e) {
e.maybeRethrow(DateTimeException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (time == null) {
// This is not expected.
throw new DateTimeException("Network based time is not available.");
}
long currentMillis = elapsedRealtime();
long deltaMs = currentMillis - time.getElapsedRealtimeMillis();
return time.getUnixEpochTimeMillis() + deltaMs;
} else {
throw new RuntimeException(new DeadSystemException());
}
}
private static IAlarmManager getIAlarmManager() {
if (sIAlarmManager == null) {
sIAlarmManager = IAlarmManager.Stub
.asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
}
return sIAlarmManager;
}
/**
/**
* Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC,
* synchronized using a remote network source outside the device.
* <p>
* While the time returned by {@link System#currentTimeMillis()} can be
* adjusted by the user, the time returned by this method cannot be adjusted
* by the user. Note that synchronization may occur using an insecure
* network protocol, so the returned time should not be used for security
* purposes.
* by the user.
* <p>
* This performs no blocking network operations and returns values based on
* a recent successful synchronization event; it will either return a valid
* time or throw.
* <p>
* Note that synchronization may occur using an insecure network protocol,
* so the returned time should not be used for security purposes.
* The device may resynchronize with the same or different network source
* at any time. Due to network delays, variations between servers, or local
* (client side) clock drift, the accuracy of the returned times cannot be
* guaranteed. In extreme cases, consecutive calls to {@link
* Clock#millis()} on the returned {@link Clock}could return times that are
* out of order.
*
* @throws DateTimeException when no accurate network time can be provided.
* @throws DateTimeException when no network time can be provided.
*/
public static @NonNull Clock currentNetworkTimeClock() {
return new SimpleClock(ZoneOffset.UTC) {

View File

@@ -55,18 +55,19 @@ public class NtpTrustedTime implements TrustedTime {
* @hide
*/
public static class TimeResult {
private final long mTimeMillis;
private final long mUnixEpochTimeMillis;
private final long mElapsedRealtimeMillis;
private final long mCertaintyMillis;
public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
mTimeMillis = timeMillis;
public TimeResult(
long unixEpochTimeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
mUnixEpochTimeMillis = unixEpochTimeMillis;
mElapsedRealtimeMillis = elapsedRealtimeMillis;
mCertaintyMillis = certaintyMillis;
}
public long getTimeMillis() {
return mTimeMillis;
return mUnixEpochTimeMillis;
}
public long getElapsedRealtimeMillis() {
@@ -77,9 +78,11 @@ public class NtpTrustedTime implements TrustedTime {
return mCertaintyMillis;
}
/** Calculates and returns the current time accounting for the age of this result. */
/**
* Calculates and returns the current Unix epoch time accounting for the age of this result.
*/
public long currentTimeMillis() {
return mTimeMillis + getAgeMillis();
return mUnixEpochTimeMillis + getAgeMillis();
}
/** Calculates and returns the age of this result. */
@@ -99,7 +102,7 @@ public class NtpTrustedTime implements TrustedTime {
@Override
public String toString() {
return "TimeResult{"
+ "mTimeMillis=" + Instant.ofEpochMilli(mTimeMillis)
+ "mUnixEpochTimeMillis=" + Instant.ofEpochMilli(mUnixEpochTimeMillis)
+ ", mElapsedRealtimeMillis=" + Duration.ofMillis(mElapsedRealtimeMillis)
+ ", mCertaintyMillis=" + mCertaintyMillis
+ '}';

View File

@@ -29,15 +29,18 @@ import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.NtpTrustedTime;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -49,6 +52,7 @@ import com.android.server.timezonedetector.CallerIdentityInjector;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.DateTimeException;
import java.util.Objects;
/**
@@ -93,6 +97,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
@NonNull private final CallerIdentityInjector mCallerIdentityInjector;
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
@NonNull private final NtpTrustedTime mNtpTrustedTime;
/**
* Holds the listeners. The key is the {@link IBinder} associated with the listener, the value
@@ -107,19 +112,21 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
this(context, handler, serviceConfigAccessor, timeDetectorStrategy,
CallerIdentityInjector.REAL);
CallerIdentityInjector.REAL, NtpTrustedTime.getInstance(context));
}
@VisibleForTesting
public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeDetectorStrategy timeDetectorStrategy,
@NonNull CallerIdentityInjector callerIdentityInjector) {
@NonNull CallerIdentityInjector callerIdentityInjector,
@NonNull NtpTrustedTime ntpTrustedTime) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
mNtpTrustedTime = Objects.requireNonNull(ntpTrustedTime);
// Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
// the configuration changes for any reason.
@@ -307,6 +314,19 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub
mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
}
@Override
public TimePoint latestNetworkTime() {
// TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
// be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
// detector. mNtpTrustedTime can be removed once this happens.
NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
if (ntpResult != null) {
return new TimePoint(ntpResult.getTimeMillis(), ntpResult.getElapsedRealtimeMillis());
} else {
throw new ParcelableException(new DateTimeException("Missing network time fix"));
}
}
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {

View File

@@ -19,6 +19,7 @@ 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.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -41,12 +42,15 @@ import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
import android.app.timedetector.TimePoint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelableException;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
import android.util.NtpTrustedTime;
import androidx.test.runner.AndroidJUnit4;
@@ -77,6 +81,7 @@ public class TimeDetectorServiceTest {
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
private FakeServiceConfigAccessor mFakeServiceConfigAccessor;
private NtpTrustedTime mMockNtpTrustedTime;
private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy;
@@ -94,10 +99,11 @@ public class TimeDetectorServiceTest {
mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy();
mFakeServiceConfigAccessor = new FakeServiceConfigAccessor();
mMockNtpTrustedTime = mock(NtpTrustedTime.class);
mTimeDetectorService = new TimeDetectorService(
mMockContext, mTestHandler, mFakeServiceConfigAccessor,
mStubbedTimeDetectorStrategy, mTestCallerIdentityInjector);
mStubbedTimeDetectorStrategy, mTestCallerIdentityInjector, mMockNtpTrustedTime);
}
@After
@@ -399,6 +405,23 @@ public class TimeDetectorServiceTest {
mStubbedTimeDetectorStrategy.verifySuggestExternalTimeCalled(externalTimeSuggestion);
}
@Test
public void testLatestNetworkTime() {
NtpTrustedTime.TimeResult latestNetworkTime =
new NtpTrustedTime.TimeResult(1234L, 54321L, 999L);
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
TimePoint expected = new TimePoint(latestNetworkTime.getTimeMillis(),
latestNetworkTime.getElapsedRealtimeMillis());
assertEquals(expected, mTimeDetectorService.latestNetworkTime());
}
@Test
public void testLatestNetworkTime_noTimeAvailable() {
when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
}
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))