blob: 7bdc8a32815aab1b1d4be8a60554b4ae86787302 [file] [log] [blame]
/*
* Copyright (C) 2018 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.TimeSignal;
import android.content.Intent;
import android.util.Slog;
import android.util.TimestampedValue;
import com.android.internal.telephony.TelephonyIntents;
import java.io.PrintWriter;
/**
* An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
* {@link AlarmManager}. The TimeDetectorService handles thread safety: all calls to
* this class can be assumed to be single threaded (though the thread used may vary).
*/
// @NotThreadSafe
public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";
/**
* CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the
* actual system clock time before a warning is logged. Used to help identify situations where
* there is something other than this class setting the system clock.
*/
private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
// @NonNull after initialize()
private Callback mCallback;
// NITZ state.
@Nullable private TimestampedValue<Long> mLastNitzTime;
// Information about the last time signal received: Used when toggling auto-time.
@Nullable private TimestampedValue<Long> mLastSystemClockTime;
private boolean mLastSystemClockTimeSendNetworkBroadcast;
// System clock state.
@Nullable private TimestampedValue<Long> mLastSystemClockTimeSet;
@Override
public void initialize(@NonNull Callback callback) {
mCallback = callback;
}
@Override
public void suggestTime(@NonNull TimeSignal timeSignal) {
if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal);
return;
}
// NITZ logic
TimestampedValue<Long> newNitzUtcTime = timeSignal.getUtcTime();
boolean nitzTimeIsValid = validateNewNitzTime(newNitzUtcTime, mLastNitzTime);
if (!nitzTimeIsValid) {
return;
}
// Always store the last NITZ value received, regardless of whether we go on to use it to
// update the system clock. This is so that we can validate future NITZ signals.
mLastNitzTime = newNitzUtcTime;
// System clock update logic.
// Historically, Android has sent a telephony broadcast only when setting the time using
// NITZ.
final boolean sendNetworkBroadcast =
TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId());
final TimestampedValue<Long> newUtcTime = newNitzUtcTime;
setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
}
private static boolean validateNewNitzTime(TimestampedValue<Long> newNitzUtcTime,
TimestampedValue<Long> lastNitzTime) {
if (lastNitzTime != null) {
long referenceTimeDifference =
TimestampedValue.referenceTimeDifference(newNitzUtcTime, lastNitzTime);
if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
// Out of order or bogus.
Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received."
+ " referenceTimeDifference=" + referenceTimeDifference
+ " lastNitzTime=" + lastNitzTime
+ " newNitzUtcTime=" + newNitzUtcTime);
return false;
}
}
return true;
}
private void setSystemClockIfRequired(
TimestampedValue<Long> time, boolean sendNetworkBroadcast) {
// Store the last candidate we've seen in all cases so we can set the system clock
// when/if time detection is enabled.
mLastSystemClockTime = time;
mLastSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
if (!mCallback.isTimeDetectionEnabled()) {
Slog.d(TAG, "setSystemClockIfRequired: Time detection is not enabled. time=" + time);
return;
}
mCallback.acquireWakeLock();
try {
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
long actualTimeMillis = mCallback.systemClockMillis();
// CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
// may be setting the clock.
if (mLastSystemClockTimeSet != null) {
long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
mLastSystemClockTimeSet, elapsedRealtimeMillis);
long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
Slog.w(TAG, "System clock has not tracked elapsed real time clock. A clock may"
+ " be inaccurate or something unexpectedly set the system clock."
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " expectedTimeMillis=" + expectedTimeMillis
+ " actualTimeMillis=" + actualTimeMillis);
}
}
final String reason = "New time signal";
adjustAndSetDeviceSystemClock(
time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, reason);
} finally {
mCallback.releaseWakeLock();
}
}
@Override
public void handleAutoTimeDetectionToggle(boolean enabled) {
// If automatic time detection is enabled we update the system clock instantly if we can.
// Conversely, if automatic time detection is disabled we leave the clock as it is.
if (enabled) {
if (mLastSystemClockTime != null) {
// Only send the network broadcast if the last candidate would have caused one.
final boolean sendNetworkBroadcast = mLastSystemClockTimeSendNetworkBroadcast;
mCallback.acquireWakeLock();
try {
long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
long actualTimeMillis = mCallback.systemClockMillis();
final String reason = "Automatic time detection enabled.";
adjustAndSetDeviceSystemClock(mLastSystemClockTime, sendNetworkBroadcast,
elapsedRealtimeMillis, actualTimeMillis, reason);
} finally {
mCallback.releaseWakeLock();
}
}
} else {
// CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
// it should be in future.
mLastSystemClockTimeSet = null;
}
}
@Override
public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
pw.println("mLastNitzTime=" + mLastNitzTime);
pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
pw.println("mLastSystemClockTimeSendNetworkBroadcast="
+ mLastSystemClockTimeSendNetworkBroadcast);
}
private void adjustAndSetDeviceSystemClock(
TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
long elapsedRealtimeMillis, long actualSystemClockMillis, String reason) {
// Adjust for the time that has elapsed since the signal was received.
long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
// Check if the new signal would make sufficient difference to the system clock. If it's
// below the threshold then ignore it.
long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
if (absTimeDifference < systemClockUpdateThreshold) {
Slog.d(TAG, "adjustAndSetDeviceSystemClock: Not setting system clock. New time and"
+ " system clock are close enough."
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " newTime=" + newTime
+ " reason=" + reason
+ " systemClockUpdateThreshold=" + systemClockUpdateThreshold
+ " absTimeDifference=" + absTimeDifference);
return;
}
Slog.d(TAG, "Setting system clock using time=" + newTime
+ " reason=" + reason
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ " newTimeMillis=" + newSystemClockMillis);
mCallback.setSystemClock(newSystemClockMillis);
// CLOCK_PARANOIA : Record the last time this class set the system clock.
mLastSystemClockTimeSet = newTime;
if (sendNetworkBroadcast) {
// Send a broadcast that telephony code used to send after setting the clock.
// TODO Remove this broadcast as soon as there are no remaining listeners.
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time", newSystemClockMillis);
mCallback.sendStickyBroadcast(intent);
}
}
}