Simple pass-through TimeDetectorService

This is sufficient to wire up time detection from telephony
to the new service without breaking time detection.

This cherry-pick contains a small change: to use
SystemClock.elapsedRealtime() instead of the newer
SystemClock.elapsedRealtimeClock() with Clock.millis().

Bug: 78217059
Test: atest FrameworksServicesTests:com.android.server.timedetector
Test: atest FrameworksCoreTests:android.util.TimestampedValueTest
Merged-In: Id7175878dc22e5272c31f3e478af4b0e4183b62b
Change-Id: Id7175878dc22e5272c31f3e478af4b0e4183b62b
(cherry picked from commit 24836bfb1564b1f98c322108a924965a8dbbee4f)
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index bdbe111..f624446 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,6 +16,8 @@
 
 package android.app.timedetector;
 
+import android.app.timedetector.TimeSignal;
+
 /**
  * System private API to comunicate with time detector service.
  *
@@ -30,5 +32,5 @@
  * {@hide}
  */
 interface ITimeDetectorService {
-  void stubbedCall();
+  void suggestTime(in TimeSignal timeSignal);
 }
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index ac1e223..052050d 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -16,6 +16,7 @@
 
 package android.app.timedetector;
 
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
@@ -40,15 +41,16 @@
     }
 
     /**
-     * Does nothing.
-     * TODO: Remove this when the service implementation is built out.
+     * Suggests the current time to the detector. The detector may ignore the signal if better
+     * signals are available such as those that come from more reliable sources or were
+     * determined more recently.
      */
-    public void stubbedCall() {
+    public void suggestTime(@NonNull TimeSignal timeSignal) {
         if (DEBUG) {
-            Log.d(TAG, "stubbedCall called");
+            Log.d(TAG, "suggestTime called: " + timeSignal);
         }
         try {
-            mITimeDetectorService.stubbedCall();
+            mITimeDetectorService.suggestTime(timeSignal);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/core/java/android/app/timedetector/TimeSignal.aidl
new file mode 100644
index 0000000..d2ec357
--- /dev/null
+++ b/core/java/android/app/timedetector/TimeSignal.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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 android.app.timedetector;
+
+parcelable TimeSignal;
\ No newline at end of file
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
new file mode 100644
index 0000000..7ba03cc
--- /dev/null
+++ b/core/java/android/app/timedetector/TimeSignal.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.app.timedetector;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import java.util.Objects;
+
+/**
+ * A time signal from a named source. The value consists of the number of milliseconds elapsed since
+ * 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number was
+ * established. The elapsed realtime clock is considered accurate but volatile, so time signals
+ * must not be persisted across device resets.
+ *
+ * @hide
+ */
+public final class TimeSignal implements Parcelable {
+
+    public static final Parcelable.Creator<TimeSignal> CREATOR =
+            new Parcelable.Creator<TimeSignal>() {
+                public TimeSignal createFromParcel(Parcel in) {
+                    return TimeSignal.createFromParcel(in);
+                }
+
+                public TimeSignal[] newArray(int size) {
+                    return new TimeSignal[size];
+                }
+            };
+
+    public static final String SOURCE_ID_NITZ = "nitz";
+
+    private final String mSourceId;
+    private final TimestampedValue<Long> mUtcTime;
+
+    public TimeSignal(String sourceId, TimestampedValue<Long> utcTime) {
+        mSourceId = Objects.requireNonNull(sourceId);
+        mUtcTime = Objects.requireNonNull(utcTime);
+    }
+
+    private static TimeSignal createFromParcel(Parcel in) {
+        String sourceId = in.readString();
+        TimestampedValue<Long> utcTime =
+                TimestampedValue.readFromParcel(in, null /* classLoader */, Long.class);
+        return new TimeSignal(sourceId, utcTime);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mSourceId);
+        TimestampedValue.writeToParcel(dest, mUtcTime);
+    }
+
+    @NonNull
+    public String getSourceId() {
+        return mSourceId;
+    }
+
+    @NonNull
+    public TimestampedValue<Long> getUtcTime() {
+        return mUtcTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimeSignal that = (TimeSignal) o;
+        return Objects.equals(mSourceId, that.mSourceId)
+                && Objects.equals(mUtcTime, that.mUtcTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSourceId, mUtcTime);
+    }
+
+    @Override
+    public String toString() {
+        return "TimeSignal{"
+                + "mSourceId='" + mSourceId + '\''
+                + ", mUtcTime=" + mUtcTime
+                + '}';
+    }
+}
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/util/TimestampedValue.java
new file mode 100644
index 0000000..2160380
--- /dev/null
+++ b/core/java/android/util/TimestampedValue.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import java.util.Objects;
+
+/**
+ * A value with an associated reference time. The reference time will typically be provided by the
+ * elapsed realtime clock. The elapsed realtime clock can be obtained using methods like
+ * {@link SystemClock#elapsedRealtime()} or {@link SystemClock#elapsedRealtimeClock()}.
+ * If a suitable clock is used the reference time can be used to identify the age of a value or
+ * ordering between values.
+ *
+ * <p>To read and write a timestamped value from / to a Parcel see
+ * {@link #readFromParcel(Parcel, ClassLoader, Class)} and
+ * {@link #writeToParcel(Parcel, TimestampedValue)}.
+ *
+ * @param <T> the type of the value with an associated timestamp
+ * @hide
+ */
+public final class TimestampedValue<T> {
+    private final long mReferenceTimeMillis;
+    private final T mValue;
+
+    public TimestampedValue(long referenceTimeMillis, T value) {
+        mReferenceTimeMillis = referenceTimeMillis;
+        mValue = value;
+    }
+
+    public long getReferenceTimeMillis() {
+        return mReferenceTimeMillis;
+    }
+
+    public T getValue() {
+        return mValue;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TimestampedValue<?> that = (TimestampedValue<?>) o;
+        return mReferenceTimeMillis == that.mReferenceTimeMillis
+                && Objects.equals(mValue, that.mValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mReferenceTimeMillis, mValue);
+    }
+
+    /**
+     * Read a {@link TimestampedValue} from a parcel that was stored using
+     * {@link #writeToParcel(Parcel, TimestampedValue)}.
+     *
+     * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
+     * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
+     * supported by those methods.
+     *
+     * @param in the Parcel to read from
+     * @param classLoader the ClassLoader to pass to {@link Parcel#readValue(ClassLoader)}
+     * @param valueClass the expected type of the value, typically the same as {@code <T>} but can
+     *     also be a subclass
+     * @throws RuntimeException if the value read is not compatible with {@code valueClass} or the
+     *     object could not be read
+     */
+    @SuppressWarnings("unchecked")
+    @NonNull
+    public static <T> TimestampedValue<T> readFromParcel(
+            @NonNull Parcel in, @Nullable ClassLoader classLoader, Class<? extends T> valueClass) {
+        long referenceTimeMillis = in.readLong();
+        T value = (T) in.readValue(classLoader);
+        // Equivalent to static code: if (!(value.getClass() instanceof {valueClass})) {
+        if (value != null && !valueClass.isAssignableFrom(value.getClass())) {
+            throw new RuntimeException("Value was of type " + value.getClass()
+                    + " is not assignable to " + valueClass);
+        }
+        return new TimestampedValue<>(referenceTimeMillis, value);
+    }
+
+    /**
+     * Write a {@link TimestampedValue} to a parcel so that it can be read using
+     * {@link #readFromParcel(Parcel, ClassLoader, Class)}.
+     *
+     * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
+     * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
+     * supported by those methods.
+     *
+     * @param dest the Parcel
+     * @param timestampedValue the value
+     * @throws RuntimeException if the value could not be written to the Parcel
+     */
+    public static void writeToParcel(
+            @NonNull Parcel dest, @NonNull TimestampedValue<?> timestampedValue) {
+        dest.writeLong(timestampedValue.mReferenceTimeMillis);
+        dest.writeValue(timestampedValue.mValue);
+    }
+}
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/util/TimestampedValueTest.java
new file mode 100644
index 0000000..7117c1b
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TimestampedValueTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 android.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TimestampedValueTest {
+
+    @Test
+    public void testEqualsAndHashcode() {
+        TimestampedValue<String> one1000one = new TimestampedValue<>(1000, "one");
+        assertEqualsAndHashCode(one1000one, one1000one);
+
+        TimestampedValue<String> one1000two = new TimestampedValue<>(1000, "one");
+        assertEqualsAndHashCode(one1000one, one1000two);
+
+        TimestampedValue<String> two1000 = new TimestampedValue<>(1000, "two");
+        assertNotEquals(one1000one, two1000);
+
+        TimestampedValue<String> one2000 = new TimestampedValue<>(2000, "one");
+        assertNotEquals(one1000one, one2000);
+    }
+
+    private static void assertEqualsAndHashCode(Object one, Object two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+
+    @Test
+    public void testParceling() {
+        TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+        Parcel parcel = Parcel.obtain();
+        try {
+            TimestampedValue.writeToParcel(parcel, stringValue);
+
+            parcel.setDataPosition(0);
+
+            TimestampedValue<String> stringValueCopy =
+                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+            assertEquals(stringValue, stringValueCopy);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testParceling_valueClassOk() {
+        TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+        Parcel parcel = Parcel.obtain();
+        try {
+            TimestampedValue.writeToParcel(parcel, stringValue);
+
+            parcel.setDataPosition(0);
+
+            TimestampedValue<Object> stringValueCopy =
+                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, Object.class);
+            assertEquals(stringValue, stringValueCopy);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testParceling_valueClassIncompatible() {
+        TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+        Parcel parcel = Parcel.obtain();
+        try {
+            TimestampedValue.writeToParcel(parcel, stringValue);
+
+            parcel.setDataPosition(0);
+
+            TimestampedValue.readFromParcel(parcel, null /* classLoader */, Double.class);
+            fail();
+        } catch (RuntimeException expected) {
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testParceling_nullValue() {
+        TimestampedValue<String> nullValue = new TimestampedValue<>(1000, null);
+        Parcel parcel = Parcel.obtain();
+        try {
+            TimestampedValue.writeToParcel(parcel, nullValue);
+
+            parcel.setDataPosition(0);
+
+            TimestampedValue<Object> nullValueCopy =
+                    TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+            assertEquals(nullValue, nullValueCopy);
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
new file mode 100644
index 0000000..e5207cb
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A placeholder implementation of TimeDetectorStrategy that passes NITZ suggestions immediately
+ * to {@link AlarmManager}.
+ */
+public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
+
+    private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";
+
+    private Callback mHelper;
+
+    @Override
+    public void initialize(@NonNull Callback callback) {
+        mHelper = 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);
+            return;
+        }
+
+        mHelper.setTime(timeSignal.getUtcTime());
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
+        // No state to dump.
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0b63e29..efd49b5 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -16,15 +16,19 @@
 
 package com.android.server.timedetector;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timedetector.ITimeDetectorService;
+import android.app.timedetector.TimeSignal;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 
-import android.app.timedetector.ITimeDetectorService;
-import android.content.Context;
-import android.util.Slog;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Objects;
 
 public final class TimeDetectorService extends ITimeDetectorService.Stub {
 
@@ -47,26 +51,36 @@
     }
 
     private final Context mContext;
+    private final TimeDetectorStrategy mTimeDetectorStrategy;
 
     private static TimeDetectorService create(Context context) {
-        return new TimeDetectorService(context);
+        TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
+        timeDetector.initialize(new TimeDetectorStrategyCallbackImpl(context));
+        return new TimeDetectorService(context, timeDetector);
     }
 
-    public TimeDetectorService(Context context) {
-        mContext = context;
+    @VisibleForTesting
+    public TimeDetectorService(@NonNull Context context,
+            @NonNull TimeDetectorStrategy timeDetectorStrategy) {
+        mContext = Objects.requireNonNull(context);
+        mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
     }
 
     @Override
-    public void stubbedCall() {
-        // Empty call for initial tests.
-        Slog.d(TAG, "stubbedCall() called");
-        // TODO(nfuller): Remove when there are real methods.
+    public void suggestTime(@NonNull TimeSignal timeSignal) {
+        enforceSetTimePermission();
+        mTimeDetectorStrategy.suggestTime(timeSignal);
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+            @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        // TODO(nfuller): Implement when there is state.
+        mTimeDetectorStrategy.dump(fd, pw, args);
+    }
+
+    private void enforceSetTimePermission() {
+        mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
     }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
new file mode 100644
index 0000000..5cb2eed
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.timedetector.TimeSignal;
+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.
+ *
+ * @hide
+ */
+public interface TimeDetectorStrategy {
+
+    interface Callback {
+        void setTime(TimestampedValue<Long> time);
+    }
+
+    void initialize(@NonNull Callback callback);
+    void suggestTime(@NonNull TimeSignal timeSignal);
+    void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args);
+
+    // Utility methods below are to be moved to a better home when one becomes more obvious.
+
+    /**
+     * Adjusts the supplied time value by applying the difference between the reference time
+     * supplied and the reference time associated with the time.
+     */
+    static long getTimeAt(@NonNull TimestampedValue<Long> timeValue, long referenceClockMillisNow) {
+        return (referenceClockMillisNow - timeValue.getReferenceTimeMillis())
+                + timeValue.getValue();
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
new file mode 100644
index 0000000..568d73a
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -0,0 +1,58 @@
+/*
+ * 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.app.AlarmManager;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.TimestampedValue;
+
+/**
+ * The real implementation of {@link TimeDetectorStrategy.Callback} used on device.
+ */
+public class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrategy.Callback {
+
+    private final static String TAG = "timedetector.TimeDetectorStrategyCallbackImpl";
+
+    @NonNull private PowerManager.WakeLock mWakeLock;
+    @NonNull private AlarmManager mAlarmManager;
+
+    public TimeDetectorStrategyCallbackImpl(Context context) {
+        PowerManager powerManager = context.getSystemService(PowerManager.class);
+
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+    }
+
+    @Override
+    public void setTime(TimestampedValue<Long> time) {
+        mWakeLock.acquire();
+        try {
+            long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
+            long currentTimeMillis = TimeDetectorStrategy.getTimeAt(time, elapsedRealtimeMillis);
+            Slog.d(TAG, "Setting system clock using time=" + time
+                    + ", elapsedRealtimeMillis=" + elapsedRealtimeMillis);
+            mAlarmManager.setTime(currentTimeMillis);
+        } finally {
+            mWakeLock.release();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
new file mode 100644
index 0000000..e4b3b13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.timedetector.TimeSignal;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.TimestampedValue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimpleTimeZoneDetectorStrategyTest {
+
+    private TimeDetectorStrategy.Callback mMockCallback;
+
+    private SimpleTimeDetectorStrategy mSimpleTimeZoneDetectorStrategy;
+
+    @Before
+    public void setUp() {
+        mMockCallback = mock(TimeDetectorStrategy.Callback.class);
+        mSimpleTimeZoneDetectorStrategy = new SimpleTimeDetectorStrategy();
+        mSimpleTimeZoneDetectorStrategy.initialize(mMockCallback);
+    }
+
+    @Test
+    public void testSuggestTime_nitz() {
+        TimestampedValue<Long> utcTime = createUtcTime();
+        TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime);
+
+        mSimpleTimeZoneDetectorStrategy.suggestTime(timeSignal);
+
+        verify(mMockCallback).setTime(utcTime);
+    }
+
+    @Test
+    public void testSuggestTime_unknownSource() {
+        TimestampedValue<Long> utcTime = createUtcTime();
+        TimeSignal timeSignal = new TimeSignal("unknown", utcTime);
+        mSimpleTimeZoneDetectorStrategy.suggestTime(timeSignal);
+
+        verify(mMockCallback, never()).setTime(any());
+    }
+
+    private static TimestampedValue<Long> createUtcTime() {
+        return new TimestampedValue<>(321L, 123456L);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index dc17eeb..22dea92 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -16,31 +16,73 @@
 
 package com.android.server.timedetector;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.app.timedetector.TimeSignal;
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.TimestampedValue;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-/**
- * Unit tests for the {@link TimeDetectorService}.
- */
 @RunWith(AndroidJUnit4.class)
 public class TimeDetectorServiceTest {
 
     private TimeDetectorService mTimeDetectorService;
 
+    private Context mMockContext;
+    private TimeDetectorStrategy mMockTimeDetectorStrategy;
+
     @Before
     public void setUp() {
-        final Context context = InstrumentationRegistry.getContext();
-        mTimeDetectorService = new TimeDetectorService(context);
+        mMockContext = mock(Context.class);
+        mMockTimeDetectorStrategy = mock(TimeDetectorStrategy.class);
+        mTimeDetectorService = new TimeDetectorService(mMockContext, mMockTimeDetectorStrategy);
+    }
+
+    @After
+    public void tearDown() {
+        verifyNoMoreInteractions(mMockContext, mMockTimeDetectorStrategy);
+    }
+
+    @Test(expected=SecurityException.class)
+    public void testStubbedCall_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        TimeSignal timeSignal = createNitzTimeSignal();
+
+        try {
+            mTimeDetectorService.suggestTime(timeSignal);
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.SET_TIME), anyString());
+        }
     }
 
     @Test
-    public void testStubbedCall() {
-        mTimeDetectorService.stubbedCall();
+    public void testSuggestTime() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        TimeSignal timeSignal = createNitzTimeSignal();
+        mTimeDetectorService.suggestTime(timeSignal);
+
+        verify(mMockContext)
+                .enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString());
+        verify(mMockTimeDetectorStrategy).suggestTime(timeSignal);
     }
 
+    private static TimeSignal createNitzTimeSignal() {
+        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        return new TimeSignal(TimeSignal.SOURCE_ID_NITZ, timeValue);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
new file mode 100644
index 0000000..301ded4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.util.TimestampedValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeDetectorStrategyTest {
+
+    @Test
+    public void testGetTimeAt() {
+        long timeMillis = 1000L;
+        int referenceTimeMillis = 100;
+        TimestampedValue<Long> timestampedValue =
+                new TimestampedValue<>(referenceTimeMillis, timeMillis);
+        // Reference time is after the timestamp.
+        assertEquals(
+                timeMillis + (125 - referenceTimeMillis),
+                TimeDetectorStrategy.getTimeAt(timestampedValue, 125));
+
+        // Reference time is before the timestamp.
+        assertEquals(
+                timeMillis + (75 - referenceTimeMillis),
+                TimeDetectorStrategy.getTimeAt(timestampedValue, 75));
+    }
+}