Loading services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java +19 −2 Original line number Diff line number Diff line Loading @@ -31,6 +31,14 @@ import java.io.PrintWriter; */ abstract class NetworkTimeHelper { /** * This compile-time value can be changed to switch between new and old ways to obtain network * time for GNSS. If you have to turn this from {@code true} to {@code false} then please create * a platform bug. This switch will be removed in a future release. If there are problems with * the new impl we'd like to hear about them. */ static final boolean USE_TIME_DETECTOR_IMPL = false; /** * The callback interface used by {@link NetworkTimeHelper} to report the time to {@link * GnssLocationProvider}. The callback can happen at any time using the thread associated with Loading @@ -47,8 +55,14 @@ abstract class NetworkTimeHelper { static NetworkTimeHelper create( @NonNull Context context, @NonNull Looper looper, @NonNull InjectTimeCallback injectTimeCallback) { if (USE_TIME_DETECTOR_IMPL) { TimeDetectorNetworkTimeHelper.Environment environment = new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper); return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback); } else { return new NtpNetworkTimeHelper(context, looper, injectTimeCallback); } } /** * Sets the "on demand time injection" mode. Loading @@ -74,7 +88,9 @@ abstract class NetworkTimeHelper { * Notifies that network connectivity has been established. * * <p>Called by {@link GnssLocationProvider} when the device establishes a data network * connection. * connection. This call should be removed eventually because it should be handled by the {@link * NetworkTimeHelper} implementation itself, but has been retained for compatibility while * switching implementations. */ abstract void onNetworkAvailable(); Loading @@ -82,4 +98,5 @@ abstract class NetworkTimeHelper { * Dumps internal state during bugreports useful for debugging. */ abstract void dump(@NonNull PrintWriter pw); } services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java 0 → 100644 +339 −0 Original line number Diff line number Diff line /* * 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.location.gnss; import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.time.UnixEpochTime; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.timedetector.NetworkTimeSuggestion; import com.android.server.timedetector.TimeDetectorInternal; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.util.Objects; /** * Handles injecting network time to GNSS by using information from the platform time detector. */ public class TimeDetectorNetworkTimeHelper extends NetworkTimeHelper { /** Returns {@code true} if the TimeDetectorNetworkTimeHelper is being used. */ public static boolean isInUse() { return NetworkTimeHelper.USE_TIME_DETECTOR_IMPL; } /** * An interface exposed for easier testing that the surrounding class uses for interacting with * platform services, handlers, etc. */ interface Environment { /** * Returns the current elapsed realtime value. The same as calling {@link * SystemClock#elapsedRealtime()} but easier to fake in tests. */ @ElapsedRealtimeLong long elapsedRealtimeMillis(); /** * Returns the latest / best network time available from the time detector service. */ @Nullable NetworkTimeSuggestion getLatestNetworkTime(); /** * Sets a listener that will receive a callback when the value returned by {@link * #getLatestNetworkTime()} has changed. */ void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener); /** * Requests asynchronous execution of {@link * TimeDetectorNetworkTimeHelper#queryAndInjectNetworkTime}, to execute as soon as possible. * The thread used is the same as used by {@link #requestDelayedTimeQueryCallback}. * Only one immediate callback can be requested at a time; requesting a new immediate * callback will clear any previously requested one. */ void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason); /** * Requests a delayed call to * {@link TimeDetectorNetworkTimeHelper#delayedQueryAndInjectNetworkTime()}. * The thread used is the same as used by {@link #requestImmediateTimeQueryCallback}. * Only one delayed callback can be scheduled at a time; requesting a new delayed callback * will clear any previously requested one. */ void requestDelayedTimeQueryCallback( TimeDetectorNetworkTimeHelper helper, @DurationMillisLong long delayMillis); /** * Clear a delayed time query callback. This has no effect if no delayed callback is * currently set. */ void clearDelayedTimeQueryCallback(); } private static final String TAG = "TDNetworkTimeHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** The maximum age of a network time signal that will be passed to GNSS. */ @VisibleForTesting static final int MAX_NETWORK_TIME_AGE_MILLIS = 24 * 60 * 60 * 1000; /** * The maximum time that is allowed to pass before a network time signal should be evaluated to * be passed to GNSS when mOnDemandTimeInjection == false. */ static final int NTP_REFRESH_INTERVAL_MILLIS = MAX_NETWORK_TIME_AGE_MILLIS; private final LocalLog mDumpLog = new LocalLog(10, /*useLocalTimestamps=*/false); /** The object the helper uses to interact with other components. */ @NonNull private final Environment mEnvironment; @NonNull private final InjectTimeCallback mInjectTimeCallback; /** Set to true if the GNSS engine requested on-demand NTP time injections. */ @GuardedBy("this") private boolean mPeriodicTimeInjectionEnabled; /** * Set to true when a network time has been injected. Used to ensure that a network time is * injected if this object wasn't listening when a network time signal first became available. */ @GuardedBy("this") private boolean mNetworkTimeInjected; TimeDetectorNetworkTimeHelper( @NonNull Environment environment, @NonNull InjectTimeCallback injectTimeCallback) { mInjectTimeCallback = Objects.requireNonNull(injectTimeCallback); mEnvironment = Objects.requireNonNull(environment); // Start listening for new network time updates immediately. mEnvironment.setNetworkTimeUpdateListener(this::onNetworkTimeAvailable); } @Override synchronized void setPeriodicTimeInjectionMode(boolean periodicTimeInjectionEnabled) { // Periodic time injection has a complicated history. See b/73893222. When it is true, it // doesn't mean ONLY send it periodically. // // periodicTimeInjectionEnabled == true means the GNSS would like to be told the time // periodically in addition to all the other triggers (e.g. network available). mPeriodicTimeInjectionEnabled = periodicTimeInjectionEnabled; if (!periodicTimeInjectionEnabled) { // Cancel any previously scheduled periodic query. removePeriodicNetworkTimeQuery(); } // Inject the latest network time in all cases if it is available. // Calling queryAndInjectNetworkTime() will cause a time signal to be injected if one is // available AND will cause the next periodic query to be scheduled. String reason = "setPeriodicTimeInjectionMode(" + periodicTimeInjectionEnabled + ")"; mEnvironment.requestImmediateTimeQueryCallback(this, reason); } void onNetworkTimeAvailable() { // A new network time could become available at any time. Make sure it is passed to GNSS. mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkTimeAvailable"); } @Override void onNetworkAvailable() { // In the original NetworkTimeHelper implementation, onNetworkAvailable() would cause an NTP // refresh to be made if it had previously been blocked by network issues. This // implementation generally relies on components associated with the time detector to // monitor the network and call onNetworkTimeAvailable() when a time is available. However, // it also checks mNetworkTimeInjected in case this component wasn't listening for // onNetworkTimeAvailable() when the last one became available. synchronized (this) { if (!mNetworkTimeInjected) { // Guard against ordering issues: This check should ensure that if a network time // became available before this class started listening then the initial network // time will still be injected. mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkAvailable"); } } } @Override void demandUtcTimeInjection() { mEnvironment.requestImmediateTimeQueryCallback(this, "demandUtcTimeInjection"); } // This method should always be invoked on the mEnvironment thread. void delayedQueryAndInjectNetworkTime() { queryAndInjectNetworkTime("delayedTimeQueryCallback"); } // This method should always be invoked on the mEnvironment thread. synchronized void queryAndInjectNetworkTime(@NonNull String reason) { NetworkTimeSuggestion latestNetworkTime = mEnvironment.getLatestNetworkTime(); maybeInjectNetworkTime(latestNetworkTime, reason); // Deschedule (if needed) any previously scheduled periodic query. removePeriodicNetworkTimeQuery(); if (mPeriodicTimeInjectionEnabled) { int maxDelayMillis = NTP_REFRESH_INTERVAL_MILLIS; String debugMsg = "queryAndInjectNtpTime: Scheduling periodic query" + " reason=" + reason + " latestNetworkTime=" + latestNetworkTime + " maxDelayMillis=" + maxDelayMillis; logToDumpLog(debugMsg); // GNSS is expecting periodic injections, so schedule the next one. mEnvironment.requestDelayedTimeQueryCallback(this, maxDelayMillis); } } private long calculateTimeSignalAgeMillis( @Nullable NetworkTimeSuggestion networkTimeSuggestion) { if (networkTimeSuggestion == null) { return Long.MAX_VALUE; } long suggestionElapsedRealtimeMillis = networkTimeSuggestion.getUnixEpochTime().getElapsedRealtimeMillis(); long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); return currentElapsedRealtimeMillis - suggestionElapsedRealtimeMillis; } @GuardedBy("this") private void maybeInjectNetworkTime( @Nullable NetworkTimeSuggestion latestNetworkTime, @NonNull String reason) { // Historically, time would only be injected if it was under a certain age. This has been // kept in case it is assumed by GNSS implementations. if (calculateTimeSignalAgeMillis(latestNetworkTime) > MAX_NETWORK_TIME_AGE_MILLIS) { String debugMsg = "maybeInjectNetworkTime: Not injecting latest network time" + " latestNetworkTime=" + latestNetworkTime + " reason=" + reason; logToDumpLog(debugMsg); return; } UnixEpochTime unixEpochTime = latestNetworkTime.getUnixEpochTime(); long unixEpochTimeMillis = unixEpochTime.getUnixEpochTimeMillis(); long currentTimeMillis = System.currentTimeMillis(); String debugMsg = "maybeInjectNetworkTime: Injecting latest network time" + " latestNetworkTime=" + latestNetworkTime + " reason=" + reason + " System time offset millis=" + (unixEpochTimeMillis - currentTimeMillis); logToDumpLog(debugMsg); long timeReferenceMillis = unixEpochTime.getElapsedRealtimeMillis(); int uncertaintyMillis = latestNetworkTime.getUncertaintyMillis(); mInjectTimeCallback.injectTime(unixEpochTimeMillis, timeReferenceMillis, uncertaintyMillis); mNetworkTimeInjected = true; } @Override void dump(@NonNull PrintWriter pw) { pw.println("TimeDetectorNetworkTimeHelper:"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.increaseIndent(); synchronized (this) { ipw.println("mPeriodicTimeInjectionEnabled=" + mPeriodicTimeInjectionEnabled); } ipw.println("Debug log:"); mDumpLog.dump(ipw); } private void logToDumpLog(@NonNull String message) { mDumpLog.log(message); if (DEBUG) { Log.d(TAG, message); } } private void removePeriodicNetworkTimeQuery() { // De-schedule any previously scheduled refresh. This is idempotent and has no effect if // there isn't one. mEnvironment.clearDelayedTimeQueryCallback(); } /** The real implementation of {@link Environment} used outside of tests. */ static class EnvironmentImpl implements Environment { /** Used to ensure one scheduled runnable is queued at a time. */ private final Object mScheduledRunnableToken = new Object(); /** Used to ensure one immediate runnable is queued at a time. */ private final Object mImmediateRunnableToken = new Object(); private final Handler mHandler; private final TimeDetectorInternal mTimeDetectorInternal; EnvironmentImpl(Looper looper) { mHandler = new Handler(looper); mTimeDetectorInternal = LocalServices.getService(TimeDetectorInternal.class); } @Override public long elapsedRealtimeMillis() { return SystemClock.elapsedRealtime(); } @Override public NetworkTimeSuggestion getLatestNetworkTime() { return mTimeDetectorInternal.getLatestNetworkSuggestion(); } @Override public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) { mTimeDetectorInternal.addNetworkTimeUpdateListener(stateChangeListener); } @Override public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason) { // Ensure only one immediate callback is scheduled at a time. There's no // post(Runnable, Object), so we postDelayed() with a zero wait. synchronized (this) { mHandler.removeCallbacksAndMessages(mImmediateRunnableToken); mHandler.postDelayed(() -> helper.queryAndInjectNetworkTime(reason), mImmediateRunnableToken, 0); } } @Override public void requestDelayedTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, long delayMillis) { synchronized (this) { clearDelayedTimeQueryCallback(); mHandler.postDelayed(helper::delayedQueryAndInjectNetworkTime, mScheduledRunnableToken, delayMillis); } } @Override public synchronized void clearDelayedTimeQueryCallback() { mHandler.removeCallbacksAndMessages(mScheduledRunnableToken); } } } services/core/java/com/android/server/timedetector/EnvironmentImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -129,4 +129,9 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { public void dumpDebugLog(@NonNull PrintWriter printWriter) { SystemClockTime.dump(printWriter); } @Override public void runAsync(@NonNull Runnable runnable) { mHandler.post(runnable); } } services/core/java/com/android/server/timedetector/TimeDetectorInternal.java +19 −1 Original line number Diff line number Diff line Loading @@ -17,10 +17,13 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.ManualTimeSuggestion; import com.android.server.timezonedetector.StateChangeListener; /** * The internal (in-process) system server API for the time detector service. * Loading Loading @@ -61,10 +64,25 @@ public interface TimeDetectorInternal { /** * Suggests a network time to the time detector. The suggestion may not be used by the time * detector to set the device's time depending on device configuration and user settings, but * can replace previous network suggestions received. * can replace previous network suggestions received. See also * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and * {@link #getLatestNetworkSuggestion()}. */ void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion); /** * Adds a listener that will be notified when a new network time is available. See {@link * #getLatestNetworkSuggestion()}. */ void addNetworkTimeUpdateListener( @NonNull StateChangeListener networkSuggestionUpdateListener); /** * Returns the latest / best network time received by the time detector. */ @Nullable NetworkTimeSuggestion getLatestNetworkSuggestion(); /** * Suggests a GNSS-derived time to the time detector. The suggestion may not be used by the time * detector to set the device's time depending on device configuration and user settings, but Loading services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java +14 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.Context; import android.os.Handler; import com.android.server.timezonedetector.CurrentUserIdentityInjector; import com.android.server.timezonedetector.StateChangeListener; import java.util.Objects; Loading Loading @@ -86,6 +87,19 @@ public class TimeDetectorInternalImpl implements TimeDetectorInternal { mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(suggestion)); } @Override public void addNetworkTimeUpdateListener( @NonNull StateChangeListener networkTimeUpdateListener) { Objects.requireNonNull(networkTimeUpdateListener); mTimeDetectorStrategy.addNetworkTimeUpdateListener(networkTimeUpdateListener); } @Override @NonNull public NetworkTimeSuggestion getLatestNetworkSuggestion() { return mTimeDetectorStrategy.getLatestNetworkSuggestion(); } @Override public void suggestGnssTime(@NonNull GnssTimeSuggestion suggestion) { Objects.requireNonNull(suggestion); Loading Loading
services/core/java/com/android/server/location/gnss/NetworkTimeHelper.java +19 −2 Original line number Diff line number Diff line Loading @@ -31,6 +31,14 @@ import java.io.PrintWriter; */ abstract class NetworkTimeHelper { /** * This compile-time value can be changed to switch between new and old ways to obtain network * time for GNSS. If you have to turn this from {@code true} to {@code false} then please create * a platform bug. This switch will be removed in a future release. If there are problems with * the new impl we'd like to hear about them. */ static final boolean USE_TIME_DETECTOR_IMPL = false; /** * The callback interface used by {@link NetworkTimeHelper} to report the time to {@link * GnssLocationProvider}. The callback can happen at any time using the thread associated with Loading @@ -47,8 +55,14 @@ abstract class NetworkTimeHelper { static NetworkTimeHelper create( @NonNull Context context, @NonNull Looper looper, @NonNull InjectTimeCallback injectTimeCallback) { if (USE_TIME_DETECTOR_IMPL) { TimeDetectorNetworkTimeHelper.Environment environment = new TimeDetectorNetworkTimeHelper.EnvironmentImpl(looper); return new TimeDetectorNetworkTimeHelper(environment, injectTimeCallback); } else { return new NtpNetworkTimeHelper(context, looper, injectTimeCallback); } } /** * Sets the "on demand time injection" mode. Loading @@ -74,7 +88,9 @@ abstract class NetworkTimeHelper { * Notifies that network connectivity has been established. * * <p>Called by {@link GnssLocationProvider} when the device establishes a data network * connection. * connection. This call should be removed eventually because it should be handled by the {@link * NetworkTimeHelper} implementation itself, but has been retained for compatibility while * switching implementations. */ abstract void onNetworkAvailable(); Loading @@ -82,4 +98,5 @@ abstract class NetworkTimeHelper { * Dumps internal state during bugreports useful for debugging. */ abstract void dump(@NonNull PrintWriter pw); }
services/core/java/com/android/server/location/gnss/TimeDetectorNetworkTimeHelper.java 0 → 100644 +339 −0 Original line number Diff line number Diff line /* * 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.location.gnss; import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.time.UnixEpochTime; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.timedetector.NetworkTimeSuggestion; import com.android.server.timedetector.TimeDetectorInternal; import com.android.server.timezonedetector.StateChangeListener; import java.io.PrintWriter; import java.util.Objects; /** * Handles injecting network time to GNSS by using information from the platform time detector. */ public class TimeDetectorNetworkTimeHelper extends NetworkTimeHelper { /** Returns {@code true} if the TimeDetectorNetworkTimeHelper is being used. */ public static boolean isInUse() { return NetworkTimeHelper.USE_TIME_DETECTOR_IMPL; } /** * An interface exposed for easier testing that the surrounding class uses for interacting with * platform services, handlers, etc. */ interface Environment { /** * Returns the current elapsed realtime value. The same as calling {@link * SystemClock#elapsedRealtime()} but easier to fake in tests. */ @ElapsedRealtimeLong long elapsedRealtimeMillis(); /** * Returns the latest / best network time available from the time detector service. */ @Nullable NetworkTimeSuggestion getLatestNetworkTime(); /** * Sets a listener that will receive a callback when the value returned by {@link * #getLatestNetworkTime()} has changed. */ void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener); /** * Requests asynchronous execution of {@link * TimeDetectorNetworkTimeHelper#queryAndInjectNetworkTime}, to execute as soon as possible. * The thread used is the same as used by {@link #requestDelayedTimeQueryCallback}. * Only one immediate callback can be requested at a time; requesting a new immediate * callback will clear any previously requested one. */ void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason); /** * Requests a delayed call to * {@link TimeDetectorNetworkTimeHelper#delayedQueryAndInjectNetworkTime()}. * The thread used is the same as used by {@link #requestImmediateTimeQueryCallback}. * Only one delayed callback can be scheduled at a time; requesting a new delayed callback * will clear any previously requested one. */ void requestDelayedTimeQueryCallback( TimeDetectorNetworkTimeHelper helper, @DurationMillisLong long delayMillis); /** * Clear a delayed time query callback. This has no effect if no delayed callback is * currently set. */ void clearDelayedTimeQueryCallback(); } private static final String TAG = "TDNetworkTimeHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); /** The maximum age of a network time signal that will be passed to GNSS. */ @VisibleForTesting static final int MAX_NETWORK_TIME_AGE_MILLIS = 24 * 60 * 60 * 1000; /** * The maximum time that is allowed to pass before a network time signal should be evaluated to * be passed to GNSS when mOnDemandTimeInjection == false. */ static final int NTP_REFRESH_INTERVAL_MILLIS = MAX_NETWORK_TIME_AGE_MILLIS; private final LocalLog mDumpLog = new LocalLog(10, /*useLocalTimestamps=*/false); /** The object the helper uses to interact with other components. */ @NonNull private final Environment mEnvironment; @NonNull private final InjectTimeCallback mInjectTimeCallback; /** Set to true if the GNSS engine requested on-demand NTP time injections. */ @GuardedBy("this") private boolean mPeriodicTimeInjectionEnabled; /** * Set to true when a network time has been injected. Used to ensure that a network time is * injected if this object wasn't listening when a network time signal first became available. */ @GuardedBy("this") private boolean mNetworkTimeInjected; TimeDetectorNetworkTimeHelper( @NonNull Environment environment, @NonNull InjectTimeCallback injectTimeCallback) { mInjectTimeCallback = Objects.requireNonNull(injectTimeCallback); mEnvironment = Objects.requireNonNull(environment); // Start listening for new network time updates immediately. mEnvironment.setNetworkTimeUpdateListener(this::onNetworkTimeAvailable); } @Override synchronized void setPeriodicTimeInjectionMode(boolean periodicTimeInjectionEnabled) { // Periodic time injection has a complicated history. See b/73893222. When it is true, it // doesn't mean ONLY send it periodically. // // periodicTimeInjectionEnabled == true means the GNSS would like to be told the time // periodically in addition to all the other triggers (e.g. network available). mPeriodicTimeInjectionEnabled = periodicTimeInjectionEnabled; if (!periodicTimeInjectionEnabled) { // Cancel any previously scheduled periodic query. removePeriodicNetworkTimeQuery(); } // Inject the latest network time in all cases if it is available. // Calling queryAndInjectNetworkTime() will cause a time signal to be injected if one is // available AND will cause the next periodic query to be scheduled. String reason = "setPeriodicTimeInjectionMode(" + periodicTimeInjectionEnabled + ")"; mEnvironment.requestImmediateTimeQueryCallback(this, reason); } void onNetworkTimeAvailable() { // A new network time could become available at any time. Make sure it is passed to GNSS. mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkTimeAvailable"); } @Override void onNetworkAvailable() { // In the original NetworkTimeHelper implementation, onNetworkAvailable() would cause an NTP // refresh to be made if it had previously been blocked by network issues. This // implementation generally relies on components associated with the time detector to // monitor the network and call onNetworkTimeAvailable() when a time is available. However, // it also checks mNetworkTimeInjected in case this component wasn't listening for // onNetworkTimeAvailable() when the last one became available. synchronized (this) { if (!mNetworkTimeInjected) { // Guard against ordering issues: This check should ensure that if a network time // became available before this class started listening then the initial network // time will still be injected. mEnvironment.requestImmediateTimeQueryCallback(this, "onNetworkAvailable"); } } } @Override void demandUtcTimeInjection() { mEnvironment.requestImmediateTimeQueryCallback(this, "demandUtcTimeInjection"); } // This method should always be invoked on the mEnvironment thread. void delayedQueryAndInjectNetworkTime() { queryAndInjectNetworkTime("delayedTimeQueryCallback"); } // This method should always be invoked on the mEnvironment thread. synchronized void queryAndInjectNetworkTime(@NonNull String reason) { NetworkTimeSuggestion latestNetworkTime = mEnvironment.getLatestNetworkTime(); maybeInjectNetworkTime(latestNetworkTime, reason); // Deschedule (if needed) any previously scheduled periodic query. removePeriodicNetworkTimeQuery(); if (mPeriodicTimeInjectionEnabled) { int maxDelayMillis = NTP_REFRESH_INTERVAL_MILLIS; String debugMsg = "queryAndInjectNtpTime: Scheduling periodic query" + " reason=" + reason + " latestNetworkTime=" + latestNetworkTime + " maxDelayMillis=" + maxDelayMillis; logToDumpLog(debugMsg); // GNSS is expecting periodic injections, so schedule the next one. mEnvironment.requestDelayedTimeQueryCallback(this, maxDelayMillis); } } private long calculateTimeSignalAgeMillis( @Nullable NetworkTimeSuggestion networkTimeSuggestion) { if (networkTimeSuggestion == null) { return Long.MAX_VALUE; } long suggestionElapsedRealtimeMillis = networkTimeSuggestion.getUnixEpochTime().getElapsedRealtimeMillis(); long currentElapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis(); return currentElapsedRealtimeMillis - suggestionElapsedRealtimeMillis; } @GuardedBy("this") private void maybeInjectNetworkTime( @Nullable NetworkTimeSuggestion latestNetworkTime, @NonNull String reason) { // Historically, time would only be injected if it was under a certain age. This has been // kept in case it is assumed by GNSS implementations. if (calculateTimeSignalAgeMillis(latestNetworkTime) > MAX_NETWORK_TIME_AGE_MILLIS) { String debugMsg = "maybeInjectNetworkTime: Not injecting latest network time" + " latestNetworkTime=" + latestNetworkTime + " reason=" + reason; logToDumpLog(debugMsg); return; } UnixEpochTime unixEpochTime = latestNetworkTime.getUnixEpochTime(); long unixEpochTimeMillis = unixEpochTime.getUnixEpochTimeMillis(); long currentTimeMillis = System.currentTimeMillis(); String debugMsg = "maybeInjectNetworkTime: Injecting latest network time" + " latestNetworkTime=" + latestNetworkTime + " reason=" + reason + " System time offset millis=" + (unixEpochTimeMillis - currentTimeMillis); logToDumpLog(debugMsg); long timeReferenceMillis = unixEpochTime.getElapsedRealtimeMillis(); int uncertaintyMillis = latestNetworkTime.getUncertaintyMillis(); mInjectTimeCallback.injectTime(unixEpochTimeMillis, timeReferenceMillis, uncertaintyMillis); mNetworkTimeInjected = true; } @Override void dump(@NonNull PrintWriter pw) { pw.println("TimeDetectorNetworkTimeHelper:"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.increaseIndent(); synchronized (this) { ipw.println("mPeriodicTimeInjectionEnabled=" + mPeriodicTimeInjectionEnabled); } ipw.println("Debug log:"); mDumpLog.dump(ipw); } private void logToDumpLog(@NonNull String message) { mDumpLog.log(message); if (DEBUG) { Log.d(TAG, message); } } private void removePeriodicNetworkTimeQuery() { // De-schedule any previously scheduled refresh. This is idempotent and has no effect if // there isn't one. mEnvironment.clearDelayedTimeQueryCallback(); } /** The real implementation of {@link Environment} used outside of tests. */ static class EnvironmentImpl implements Environment { /** Used to ensure one scheduled runnable is queued at a time. */ private final Object mScheduledRunnableToken = new Object(); /** Used to ensure one immediate runnable is queued at a time. */ private final Object mImmediateRunnableToken = new Object(); private final Handler mHandler; private final TimeDetectorInternal mTimeDetectorInternal; EnvironmentImpl(Looper looper) { mHandler = new Handler(looper); mTimeDetectorInternal = LocalServices.getService(TimeDetectorInternal.class); } @Override public long elapsedRealtimeMillis() { return SystemClock.elapsedRealtime(); } @Override public NetworkTimeSuggestion getLatestNetworkTime() { return mTimeDetectorInternal.getLatestNetworkSuggestion(); } @Override public void setNetworkTimeUpdateListener(StateChangeListener stateChangeListener) { mTimeDetectorInternal.addNetworkTimeUpdateListener(stateChangeListener); } @Override public void requestImmediateTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, String reason) { // Ensure only one immediate callback is scheduled at a time. There's no // post(Runnable, Object), so we postDelayed() with a zero wait. synchronized (this) { mHandler.removeCallbacksAndMessages(mImmediateRunnableToken); mHandler.postDelayed(() -> helper.queryAndInjectNetworkTime(reason), mImmediateRunnableToken, 0); } } @Override public void requestDelayedTimeQueryCallback(TimeDetectorNetworkTimeHelper helper, long delayMillis) { synchronized (this) { clearDelayedTimeQueryCallback(); mHandler.postDelayed(helper::delayedQueryAndInjectNetworkTime, mScheduledRunnableToken, delayMillis); } } @Override public synchronized void clearDelayedTimeQueryCallback() { mHandler.removeCallbacksAndMessages(mScheduledRunnableToken); } } }
services/core/java/com/android/server/timedetector/EnvironmentImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -129,4 +129,9 @@ final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment { public void dumpDebugLog(@NonNull PrintWriter printWriter) { SystemClockTime.dump(printWriter); } @Override public void runAsync(@NonNull Runnable runnable) { mHandler.post(runnable); } }
services/core/java/com/android/server/timedetector/TimeDetectorInternal.java +19 −1 Original line number Diff line number Diff line Loading @@ -17,10 +17,13 @@ package com.android.server.timedetector; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.time.TimeCapabilitiesAndConfig; import android.app.time.TimeConfiguration; import android.app.timedetector.ManualTimeSuggestion; import com.android.server.timezonedetector.StateChangeListener; /** * The internal (in-process) system server API for the time detector service. * Loading Loading @@ -61,10 +64,25 @@ public interface TimeDetectorInternal { /** * Suggests a network time to the time detector. The suggestion may not be used by the time * detector to set the device's time depending on device configuration and user settings, but * can replace previous network suggestions received. * can replace previous network suggestions received. See also * {@link #addNetworkTimeUpdateListener(StateChangeListener)} and * {@link #getLatestNetworkSuggestion()}. */ void suggestNetworkTime(@NonNull NetworkTimeSuggestion suggestion); /** * Adds a listener that will be notified when a new network time is available. See {@link * #getLatestNetworkSuggestion()}. */ void addNetworkTimeUpdateListener( @NonNull StateChangeListener networkSuggestionUpdateListener); /** * Returns the latest / best network time received by the time detector. */ @Nullable NetworkTimeSuggestion getLatestNetworkSuggestion(); /** * Suggests a GNSS-derived time to the time detector. The suggestion may not be used by the time * detector to set the device's time depending on device configuration and user settings, but Loading
services/core/java/com/android/server/timedetector/TimeDetectorInternalImpl.java +14 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.Context; import android.os.Handler; import com.android.server.timezonedetector.CurrentUserIdentityInjector; import com.android.server.timezonedetector.StateChangeListener; import java.util.Objects; Loading Loading @@ -86,6 +87,19 @@ public class TimeDetectorInternalImpl implements TimeDetectorInternal { mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(suggestion)); } @Override public void addNetworkTimeUpdateListener( @NonNull StateChangeListener networkTimeUpdateListener) { Objects.requireNonNull(networkTimeUpdateListener); mTimeDetectorStrategy.addNetworkTimeUpdateListener(networkTimeUpdateListener); } @Override @NonNull public NetworkTimeSuggestion getLatestNetworkSuggestion() { return mTimeDetectorStrategy.getLatestNetworkSuggestion(); } @Override public void suggestGnssTime(@NonNull GnssTimeSuggestion suggestion) { Objects.requireNonNull(suggestion); Loading