diff options
13 files changed, 738 insertions, 27 deletions
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl index bdbe111c7ebe..f624446a2027 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 @@ package android.app.timedetector; * {@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 ac1e2233357c..052050df8c9a 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 @@ public final class TimeDetector { } /** - * 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 000000000000..d2ec357555bc --- /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 000000000000..7ba03cc33454 --- /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 000000000000..21603801d4cb --- /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 000000000000..7117c1b2d950 --- /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 000000000000..e5207cb81495 --- /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 0b63e29e149a..efd49b5a2c45 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 com.android.internal.util.DumpUtils; -import com.android.server.SystemService; - +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.timedetector.ITimeDetectorService; +import android.app.timedetector.TimeSignal; import android.content.Context; -import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Objects; public final class TimeDetectorService extends ITimeDetectorService.Stub { @@ -47,26 +51,36 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { } 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 000000000000..5cb2eed0932e --- /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 000000000000..568d73aed50d --- /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 000000000000..e4b3b131b409 --- /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 dc17eeb299f3..22dea92cc0b2 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 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.InstrumentationRegistry; 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 000000000000..301ded47ee0b --- /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)); + } +} |