Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.timedetector; |
| 18 | |
| 19 | import android.annotation.NonNull; |
| 20 | import android.annotation.Nullable; |
| 21 | import android.app.AlarmManager; |
| 22 | import android.app.timedetector.TimeSignal; |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 23 | import android.content.Intent; |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 24 | import android.util.Slog; |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 25 | import android.util.TimestampedValue; |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 26 | |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 27 | import com.android.internal.telephony.TelephonyIntents; |
| 28 | |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 29 | import java.io.PrintWriter; |
| 30 | |
| 31 | /** |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 32 | * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to |
| 33 | * {@link AlarmManager}. The TimeDetectorService handles thread safety: all calls to |
| 34 | * this class can be assumed to be single threaded (though the thread used may vary). |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 35 | */ |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 36 | // @NotThreadSafe |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 37 | public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { |
| 38 | |
| 39 | private final static String TAG = "timedetector.SimpleTimeDetectorStrategy"; |
| 40 | |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 41 | /** |
| 42 | * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the |
| 43 | * actual system clock time before a warning is logged. Used to help identify situations where |
| 44 | * there is something other than this class setting the system clock. |
| 45 | */ |
| 46 | private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; |
| 47 | |
| 48 | // @NonNull after initialize() |
| 49 | private Callback mCallback; |
| 50 | |
| 51 | // NITZ state. |
| 52 | @Nullable private TimestampedValue<Long> mLastNitzTime; |
| 53 | |
| 54 | |
| 55 | // Information about the last time signal received: Used when toggling auto-time. |
| 56 | @Nullable private TimestampedValue<Long> mLastSystemClockTime; |
| 57 | private boolean mLastSystemClockTimeSendNetworkBroadcast; |
| 58 | |
| 59 | // System clock state. |
| 60 | @Nullable private TimestampedValue<Long> mLastSystemClockTimeSet; |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 61 | |
| 62 | @Override |
| 63 | public void initialize(@NonNull Callback callback) { |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 64 | mCallback = callback; |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | @Override |
| 68 | public void suggestTime(@NonNull TimeSignal timeSignal) { |
| 69 | if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) { |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 70 | Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal); |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 71 | return; |
| 72 | } |
| 73 | |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 74 | // NITZ logic |
| 75 | |
| 76 | TimestampedValue<Long> newNitzUtcTime = timeSignal.getUtcTime(); |
| 77 | boolean nitzTimeIsValid = validateNewNitzTime(newNitzUtcTime, mLastNitzTime); |
| 78 | if (!nitzTimeIsValid) { |
| 79 | return; |
| 80 | } |
| 81 | // Always store the last NITZ value received, regardless of whether we go on to use it to |
| 82 | // update the system clock. This is so that we can validate future NITZ signals. |
| 83 | mLastNitzTime = newNitzUtcTime; |
| 84 | |
| 85 | // System clock update logic. |
| 86 | |
| 87 | // Historically, Android has sent a telephony broadcast only when setting the time using |
| 88 | // NITZ. |
| 89 | final boolean sendNetworkBroadcast = |
| 90 | TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId()); |
| 91 | |
| 92 | final TimestampedValue<Long> newUtcTime = newNitzUtcTime; |
| 93 | setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast); |
| 94 | } |
| 95 | |
| 96 | private static boolean validateNewNitzTime(TimestampedValue<Long> newNitzUtcTime, |
| 97 | TimestampedValue<Long> lastNitzTime) { |
| 98 | |
| 99 | if (lastNitzTime != null) { |
| 100 | long referenceTimeDifference = |
| 101 | TimestampedValue.referenceTimeDifference(newNitzUtcTime, lastNitzTime); |
| 102 | if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) { |
| 103 | // Out of order or bogus. |
| 104 | Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received." |
| 105 | + " referenceTimeDifference=" + referenceTimeDifference |
| 106 | + " lastNitzTime=" + lastNitzTime |
| 107 | + " newNitzUtcTime=" + newNitzUtcTime); |
| 108 | return false; |
| 109 | } |
| 110 | } |
| 111 | return true; |
| 112 | } |
| 113 | |
| 114 | private void setSystemClockIfRequired( |
| 115 | TimestampedValue<Long> time, boolean sendNetworkBroadcast) { |
| 116 | |
| 117 | // Store the last candidate we've seen in all cases so we can set the system clock |
| 118 | // when/if time detection is enabled. |
| 119 | mLastSystemClockTime = time; |
| 120 | mLastSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast; |
| 121 | |
| 122 | if (!mCallback.isTimeDetectionEnabled()) { |
| 123 | Slog.d(TAG, "setSystemClockIfRequired: Time detection is not enabled. time=" + time); |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | mCallback.acquireWakeLock(); |
| 128 | try { |
| 129 | long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); |
| 130 | long actualTimeMillis = mCallback.systemClockMillis(); |
| 131 | |
| 132 | // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else |
| 133 | // may be setting the clock. |
| 134 | if (mLastSystemClockTimeSet != null) { |
| 135 | long expectedTimeMillis = TimeDetectorStrategy.getTimeAt( |
| 136 | mLastSystemClockTimeSet, elapsedRealtimeMillis); |
| 137 | long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis); |
| 138 | if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) { |
| 139 | Slog.w(TAG, "System clock has not tracked elapsed real time clock. A clock may" |
| 140 | + " be inaccurate or something unexpectedly set the system clock." |
| 141 | + " elapsedRealtimeMillis=" + elapsedRealtimeMillis |
| 142 | + " expectedTimeMillis=" + expectedTimeMillis |
| 143 | + " actualTimeMillis=" + actualTimeMillis); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | final String reason = "New time signal"; |
| 148 | adjustAndSetDeviceSystemClock( |
| 149 | time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, reason); |
| 150 | } finally { |
| 151 | mCallback.releaseWakeLock(); |
| 152 | } |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | @Override |
Neil Fuller | 4980bbc | 2018-06-12 21:06:20 +0100 | [diff] [blame] | 156 | public void handleAutoTimeDetectionToggle(boolean enabled) { |
| 157 | // If automatic time detection is enabled we update the system clock instantly if we can. |
| 158 | // Conversely, if automatic time detection is disabled we leave the clock as it is. |
| 159 | if (enabled) { |
| 160 | if (mLastSystemClockTime != null) { |
| 161 | // Only send the network broadcast if the last candidate would have caused one. |
| 162 | final boolean sendNetworkBroadcast = mLastSystemClockTimeSendNetworkBroadcast; |
| 163 | |
| 164 | mCallback.acquireWakeLock(); |
| 165 | try { |
| 166 | long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); |
| 167 | long actualTimeMillis = mCallback.systemClockMillis(); |
| 168 | |
| 169 | final String reason = "Automatic time detection enabled."; |
| 170 | adjustAndSetDeviceSystemClock(mLastSystemClockTime, sendNetworkBroadcast, |
| 171 | elapsedRealtimeMillis, actualTimeMillis, reason); |
| 172 | } finally { |
| 173 | mCallback.releaseWakeLock(); |
| 174 | } |
| 175 | } |
| 176 | } else { |
| 177 | // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what |
| 178 | // it should be in future. |
| 179 | mLastSystemClockTimeSet = null; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | @Override |
| 184 | public void dump(@NonNull PrintWriter pw, @Nullable String[] args) { |
| 185 | pw.println("mLastNitzTime=" + mLastNitzTime); |
| 186 | pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet); |
| 187 | pw.println("mLastSystemClockTime=" + mLastSystemClockTime); |
| 188 | pw.println("mLastSystemClockTimeSendNetworkBroadcast=" |
| 189 | + mLastSystemClockTimeSendNetworkBroadcast); |
| 190 | } |
| 191 | |
| 192 | private void adjustAndSetDeviceSystemClock( |
| 193 | TimestampedValue<Long> newTime, boolean sendNetworkBroadcast, |
| 194 | long elapsedRealtimeMillis, long actualSystemClockMillis, String reason) { |
| 195 | |
| 196 | // Adjust for the time that has elapsed since the signal was received. |
| 197 | long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis); |
| 198 | |
| 199 | // Check if the new signal would make sufficient difference to the system clock. If it's |
| 200 | // below the threshold then ignore it. |
| 201 | long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis); |
| 202 | long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis(); |
| 203 | if (absTimeDifference < systemClockUpdateThreshold) { |
| 204 | Slog.d(TAG, "adjustAndSetDeviceSystemClock: Not setting system clock. New time and" |
| 205 | + " system clock are close enough." |
| 206 | + " elapsedRealtimeMillis=" + elapsedRealtimeMillis |
| 207 | + " newTime=" + newTime |
| 208 | + " reason=" + reason |
| 209 | + " systemClockUpdateThreshold=" + systemClockUpdateThreshold |
| 210 | + " absTimeDifference=" + absTimeDifference); |
| 211 | return; |
| 212 | } |
| 213 | |
| 214 | Slog.d(TAG, "Setting system clock using time=" + newTime |
| 215 | + " reason=" + reason |
| 216 | + " elapsedRealtimeMillis=" + elapsedRealtimeMillis |
| 217 | + " newTimeMillis=" + newSystemClockMillis); |
| 218 | mCallback.setSystemClock(newSystemClockMillis); |
| 219 | |
| 220 | // CLOCK_PARANOIA : Record the last time this class set the system clock. |
| 221 | mLastSystemClockTimeSet = newTime; |
| 222 | |
| 223 | if (sendNetworkBroadcast) { |
| 224 | // Send a broadcast that telephony code used to send after setting the clock. |
| 225 | // TODO Remove this broadcast as soon as there are no remaining listeners. |
| 226 | Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME); |
| 227 | intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); |
| 228 | intent.putExtra("time", newSystemClockMillis); |
| 229 | mCallback.sendStickyBroadcast(intent); |
| 230 | } |
Neil Fuller | 4773b9d | 2018-06-08 18:44:49 +0100 | [diff] [blame] | 231 | } |
| 232 | } |