diff options
9 files changed, 227 insertions, 46 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl index 9d11ca470397..25caf4b695bb 100644 --- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl +++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl @@ -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(); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 296c89c66e4c..c053b2ed5adb 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -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; @@ -3008,17 +3005,6 @@ public class AlarmManagerService extends SystemService { } @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, "getConfigVersion"); diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index fc7afb483142..b441359b1614 100644 --- a/core/java/android/app/timedetector/ITimeDetectorService.aidl +++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl @@ -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(); } diff --git a/core/java/android/app/timedetector/TimePoint.aidl b/core/java/android/app/timedetector/TimePoint.aidl new file mode 100644 index 000000000000..80d4bc1c34b5 --- /dev/null +++ b/core/java/android/app/timedetector/TimePoint.aidl @@ -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; diff --git a/core/java/android/app/timedetector/TimePoint.java b/core/java/android/app/timedetector/TimePoint.java new file mode 100644 index 000000000000..aa079a9b1159 --- /dev/null +++ b/core/java/android/app/timedetector/TimePoint.java @@ -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]; + } + }; +} diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 7379443877b7..ecea054db216 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -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) { diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 4e7b3a51d758..4a3f772d3bc6 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -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 + '}'; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 105cd78f2a07..b3ec27b4f0f2 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -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. @@ -308,6 +315,19 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub } @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) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index e9617e9d973a..2570158f50b2 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -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 @@ -400,6 +406,23 @@ public class TimeDetectorServiceTest { } @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)) .thenReturn(PackageManager.PERMISSION_GRANTED); |