content: /*
   * 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);
          }
      }
  }
  