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

Commit 325ecf7d authored by Neil Fuller's avatar Neil Fuller Committed by android-build-merger
Browse files

Merge "Add more behavior to TimeDetectorService"

am: b1fc5a4c

Change-Id: Ibbda2d253bf807cc480a4218aa1d898d6737ec7b
parents e01a7adf b1fc5a4c
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