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/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));
+ }
+}