Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4980bbcd authored by Neil Fuller's avatar Neil Fuller
Browse files

Add more behavior to TimeDetectorService

The client code now "suggests" time updates
to the time detection service. The current implementation
of the time detection service will validate and set the
device system clock as needed. In future it will ignore
these suggestions if better information is available.

Responsibility for sending the
TelephonyIntents.ACTION_NETWORK_SET_TIME intent has
been moved to the time detection service until it can be
removed or replaced.

The telephony code is still responsible for basic rate
limiting but the majority of the decision logic has been
moved to the service.

There is an associated change in telephony code to switch to
using the server.

Bug: 78217059
Test: atest FrameworksServicesTests:com.android.server.timedetector
Test: atest FrameworksCoreTests:android.util.TimestampedValueTest
Change-Id: I4f7a10ac06b2d32da22689e1ddf309e0a2795f30
parent 3580a6a9
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -126,4 +126,12 @@ public final class TimestampedValue<T> {
        dest.writeLong(timestampedValue.mReferenceTimeMillis);
        dest.writeValue(timestampedValue.mValue);
    }

    /**
     * Returns the difference in milliseconds between two instance's reference times.
     */
    public static long referenceTimeDifference(
            @NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
        return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -116,4 +116,14 @@ public class TimestampedValueTest {
            parcel.recycle();
        }
    }

    @Test
    public void testReferenceTimeDifference() {
        TimestampedValue<Long> value1 = new TimestampedValue<>(1000, 123L);
        assertEquals(0, TimestampedValue.referenceTimeDifference(value1, value1));

        TimestampedValue<Long> value2 = new TimestampedValue<>(1, 321L);
        assertEquals(999, TimestampedValue.referenceTimeDifference(value1, value2));
        assertEquals(-999, TimestampedValue.referenceTimeDifference(value2, value1));
    }
}
+184 −9
Original line number Diff line number Diff line
@@ -20,38 +20,213 @@ 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.FileDescriptor;
import java.io.PrintWriter;

/**
 * A placeholder implementation of TimeDetectorStrategy that passes NITZ suggestions immediately
 * to {@link AlarmManager}.
 * 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";

    private Callback mHelper;
    /**
     * 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) {
        mHelper = callback;
        mCallback = callback;
    }

    @Override
    public void suggestTime(@NonNull TimeSignal timeSignal) {
        if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
            Slog.w(TAG, "Ignoring signal from unknown source: " + timeSignal);
            Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal);
            return;
        }

        mHelper.setTime(timeSignal.getUtcTime());
        // 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 FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
        // No state to dump.
    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);
        }
    }
}
+54 −13
Original line number Diff line number Diff line
@@ -20,24 +20,29 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.TimeSignal;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Binder;
import android.provider.Settings;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.timedetector.TimeDetectorStrategy.Callback;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;

public final class TimeDetectorService extends ITimeDetectorService.Stub {

    private static final String TAG = "timedetector.TimeDetectorService";

    public static class Lifecycle extends SystemService {

        public Lifecycle(Context context) {
        public Lifecycle(@NonNull Context context) {
            super(context);
        }

@@ -51,31 +56,65 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
        }
    }

    private final Context mContext;
    private final TimeDetectorStrategy mTimeDetectorStrategy;
    @NonNull private final Context mContext;
    @NonNull private final Callback mCallback;

    // The lock used when call the strategy to ensure thread safety.
    @NonNull private final Object mStrategyLock = new Object();

    @GuardedBy("mStrategyLock")
    @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;

    private static TimeDetectorService create(@NonNull Context context) {
        final TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
        final TimeDetectorStrategyCallbackImpl callback =
                new TimeDetectorStrategyCallbackImpl(context);
        timeDetector.initialize(callback);

        TimeDetectorService timeDetectorService =
                new TimeDetectorService(context, callback, timeDetector);

        // Wire up event listening.
        ContentResolver contentResolver = context.getContentResolver();
        contentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                new ContentObserver(FgThread.getHandler()) {
                    public void onChange(boolean selfChange) {
                        timeDetectorService.handleAutoTimeDetectionToggle();
                    }
                });

    private static TimeDetectorService create(Context context) {
        TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
        timeDetector.initialize(new TimeDetectorStrategyCallbackImpl(context));
        return new TimeDetectorService(context, timeDetector);
        return timeDetectorService;
    }

    @VisibleForTesting
    public TimeDetectorService(@NonNull Context context,
    public TimeDetectorService(@NonNull Context context, @NonNull Callback callback,
            @NonNull TimeDetectorStrategy timeDetectorStrategy) {
        mContext = Objects.requireNonNull(context);
        mCallback = Objects.requireNonNull(callback);
        mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
    }

    @Override
    public void suggestTime(@NonNull TimeSignal timeSignal) {
        enforceSetTimePermission();
        Objects.requireNonNull(timeSignal);

        long callerIdToken = Binder.clearCallingIdentity();
        long idToken = Binder.clearCallingIdentity();
        try {
            synchronized (mStrategyLock) {
                mTimeDetectorStrategy.suggestTime(timeSignal);
            }
        } finally {
            Binder.restoreCallingIdentity(callerIdToken);
            Binder.restoreCallingIdentity(idToken);
        }
    }

    @VisibleForTesting
    public void handleAutoTimeDetectionToggle() {
        synchronized (mStrategyLock) {
            final boolean timeDetectionEnabled = mCallback.isTimeDetectionEnabled();
            mTimeDetectorStrategy.handleAutoTimeDetectionToggle(timeDetectionEnabled);
        }
    }

@@ -84,7 +123,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
            @Nullable String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;

        mTimeDetectorStrategy.dump(fd, pw, args);
        synchronized (mStrategyLock) {
            mTimeDetectorStrategy.dump(pw, args);
        }
    }

    private void enforceSetTimePermission() {
+44 −4
Original line number Diff line number Diff line
@@ -19,26 +19,66 @@ package com.android.server.timedetector;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.TimeSignal;
import android.content.Intent;
import android.util.TimestampedValue;

import java.io.FileDescriptor;
import java.io.PrintWriter;

/**
 * The interface for classes that implement the time detection algorithm used by the
 * TimeDetectorService.
 * TimeDetectorService. The TimeDetectorService handles thread safety: all calls to implementations
 * of this interface can be assumed to be single threaded (though the thread used may vary).
 *
 * @hide
 */
// @NotThreadSafe
public interface TimeDetectorStrategy {

    /**
     * The interface used by the strategy to interact with the surrounding service.
     */
    interface Callback {
        void setTime(TimestampedValue<Long> time);

        /**
         * The absolute threshold below which the system clock need not be updated. i.e. if setting
         * the system clock would adjust it by less than this (either backwards or forwards) then it
         * need not be set.
         */
        int systemClockUpdateThresholdMillis();

        /** Returns true if automatic time detection is enabled. */
        boolean isTimeDetectionEnabled();

        /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
        void acquireWakeLock();

        /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
        long elapsedRealtimeMillis();

        /** Returns the system clock value. The WakeLock must be held. */
        long systemClockMillis();

        /** Sets the device system clock. The WakeLock must be held. */
        void setSystemClock(long newTimeMillis);

        /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
        void releaseWakeLock();

        /** Send the supplied intent as a stick broadcast. */
        void sendStickyBroadcast(@NonNull Intent intent);
    }

    /** Initialize the strategy. */
    void initialize(@NonNull Callback callback);

    /** Process the suggested time. */
    void suggestTime(@NonNull TimeSignal timeSignal);
    void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args);

    /** Handle the auto-time setting being toggled on or off. */
    void handleAutoTimeDetectionToggle(boolean enabled);

    /** Dump debug information. */
    void dump(@NonNull PrintWriter pw, @Nullable String[] args);

    // Utility methods below are to be moved to a better home when one becomes more obvious.

Loading