Add a new method to set time

Before this change there are a various components that set the system
clock by directly calling AlarmManager.setTime(). This change exposes a
new method on TimeDetector to use when setting the device time manually
(e.g. via settings) and modifies some callers to use it.

The intent is to later restrict the number of distinct processes that
can manipulate the device system clock directly so that all time changes
go through the time detector service, which can enforce policy, log
the reasons for changes, and so on.

Bug: 140712361
Test: atest com.android.server.timedetector
Change-Id: I9300dba868ed61249d0848b0dd4b953996161bda
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index ddc4932..9877fc7 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,6 +16,7 @@
 
 package android.app.timedetector;
 
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 
 /**
@@ -33,4 +34,5 @@
  */
 interface ITimeDetectorService {
   void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
+  void suggestManualTime(in ManualTimeSuggestion timeSuggestion);
 }
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.aidl b/core/java/android/app/timedetector/ManualTimeSuggestion.aidl
new file mode 100644
index 0000000..2139404
--- /dev/null
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 ManualTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
new file mode 100644
index 0000000..e7d619a
--- /dev/null
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A time signal from a manual (user provided) 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 ManualTimeSuggestion implements Parcelable {
+
+    public static final @NonNull Creator<ManualTimeSuggestion> CREATOR =
+            new Creator<ManualTimeSuggestion>() {
+                public ManualTimeSuggestion createFromParcel(Parcel in) {
+                    return ManualTimeSuggestion.createFromParcel(in);
+                }
+
+                public ManualTimeSuggestion[] newArray(int size) {
+                    return new ManualTimeSuggestion[size];
+                }
+            };
+
+    @NonNull
+    private final TimestampedValue<Long> mUtcTime;
+    @Nullable
+    private ArrayList<String> mDebugInfo;
+
+    public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
+        mUtcTime = Objects.requireNonNull(utcTime);
+    }
+
+    private static ManualTimeSuggestion createFromParcel(Parcel in) {
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime);
+        @SuppressWarnings("unchecked")
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        suggestion.mDebugInfo = debugInfo;
+        return suggestion;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mUtcTime, 0);
+        dest.writeList(mDebugInfo);
+    }
+
+    @NonNull
+    public TimestampedValue<Long> getUtcTime() {
+        return mUtcTime;
+    }
+
+    @NonNull
+    public List<String> getDebugInfo() {
+        return Collections.unmodifiableList(mDebugInfo);
+    }
+
+    /**
+     * Associates information with the instance that can be useful for debugging / logging. The
+     * information is present in {@link #toString()} but is not considered for
+     * {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    public void addDebugInfo(String... debugInfos) {
+        if (mDebugInfo == null) {
+            mDebugInfo = new ArrayList<>();
+        }
+        mDebugInfo.addAll(Arrays.asList(debugInfos));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        ManualTimeSuggestion that = (ManualTimeSuggestion) o;
+        return Objects.equals(mUtcTime, that.mUtcTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUtcTime);
+    }
+
+    @Override
+    public String toString() {
+        return "ManualTimeSuggestion{"
+                + "mUtcTime=" + mUtcTime
+                + ", mDebugInfo=" + mDebugInfo
+                + '}';
+    }
+}
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 334e958..48d5cd2 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -17,19 +17,22 @@
 package android.app.timedetector;
 
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemClock;
 import android.util.Log;
+import android.util.TimestampedValue;
 
 /**
  * The interface through which system components can send signals to the TimeDetectorService.
  * @hide
  */
 @SystemService(Context.TIME_DETECTOR_SERVICE)
-public final class TimeDetector {
+public class TimeDetector {
     private static final String TAG = "timedetector.TimeDetector";
     private static final boolean DEBUG = false;
 
@@ -41,10 +44,11 @@
     }
 
     /**
-     * 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.
+     * Suggests the current phone-signal derived 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.
      */
+    @RequiresPermission(android.Manifest.permission.SET_TIME)
     public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
         if (DEBUG) {
             Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
@@ -56,4 +60,29 @@
         }
     }
 
+    /**
+     * Suggests the user's manually entered current time to the detector.
+     */
+    @RequiresPermission(android.Manifest.permission.SET_TIME)
+    public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) {
+        if (DEBUG) {
+            Log.d(TAG, "suggestManualTime called: " + timeSuggestion);
+        }
+        try {
+            mITimeDetectorService.suggestManualTime(timeSuggestion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * A shared utility method to create a {@link ManualTimeSuggestion}.
+     */
+    public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
+        TimestampedValue<Long> utcTime =
+                new TimestampedValue<>(SystemClock.elapsedRealtime(), when);
+        ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(utcTime);
+        manualTimeSuggestion.addDebugInfo(why);
+        return manualTimeSuggestion;
+    }
 }
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
index fd8cc24..3c79b23 100644
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
@@ -16,9 +16,11 @@
 
 package com.android.server.timedetector;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.util.Slog;
@@ -27,6 +29,8 @@
 import com.android.internal.telephony.TelephonyIntents;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
@@ -38,10 +42,22 @@
 
     private final static String TAG = "timedetector.SimpleTimeDetectorStrategy";
 
+    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    private static final int ORIGIN_PHONE = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    private static final int ORIGIN_MANUAL = 2;
+
     /**
      * 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.
+     * there is something other than this class setting the system clock automatically.
      */
     private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
 
@@ -52,11 +68,11 @@
     @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
 
     // Information about the last time signal received: Used when toggling auto-time.
-    @Nullable private TimestampedValue<Long> mLastSystemClockTime;
-    private boolean mLastSystemClockTimeSendNetworkBroadcast;
+    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
+    private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
 
     // System clock state.
-    @Nullable private TimestampedValue<Long> mLastSystemClockTimeSet;
+    @Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
 
     @Override
     public void initialize(@NonNull Callback callback) {
@@ -78,17 +94,18 @@
             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.
+        // update the system clock. This is so that we can validate future phone suggestions.
         mLastPhoneSuggestion = timeSuggestion;
 
         // System clock update logic.
-
-        // Historically, Android has sent a telephony broadcast only when setting the time using
-        // NITZ.
-        final boolean sendNetworkBroadcast = true;
-
         final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
-        setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
+        setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
+    }
+
+    @Override
+    public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
+        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
+        setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
     }
 
     private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
@@ -110,16 +127,31 @@
     }
 
     private void setSystemClockIfRequired(
-            TimestampedValue<Long> time, boolean sendNetworkBroadcast) {
+            @Origin int origin, TimestampedValue<Long> time, Object cause) {
+        // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
+        // when setting the time using NITZ.
+        boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;
 
-        // 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;
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        if (isOriginAutomatic) {
+            // Store the last auto time candidate we've seen in all cases so we can set the system
+            // clock when/if time detection is off but later enabled.
+            mLastAutoSystemClockTime = time;
+            mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
 
-        if (!mCallback.isTimeDetectionEnabled()) {
-            Slog.d(TAG, "setSystemClockIfRequired: Time detection is not enabled. time=" + time);
-            return;
+            if (!mCallback.isAutoTimeDetectionEnabled()) {
+                Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is not enabled."
+                        + " time=" + time
+                        + ", cause=" + cause);
+                return;
+            }
+        } else {
+            if (mCallback.isAutoTimeDetectionEnabled()) {
+                Slog.d(TAG, "setSystemClockIfRequired: Auto time detection is enabled."
+                        + " time=" + time
+                        + ", cause=" + cause);
+                return;
+            }
         }
 
         mCallback.acquireWakeLock();
@@ -127,37 +159,44 @@
             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);
+            if (isOriginAutomatic) {
+                // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
+                // may be setting the clock.
+                if (mLastAutoSystemClockTimeSet != null) {
+                    long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
+                            mLastAutoSystemClockTimeSet, 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);
+                    time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause);
         } finally {
             mCallback.releaseWakeLock();
         }
     }
 
+    private static boolean isOriginAutomatic(@Origin int origin) {
+        return origin == ORIGIN_PHONE;
+    }
+
     @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) {
+            if (mLastAutoSystemClockTime != null) {
                 // Only send the network broadcast if the last candidate would have caused one.
-                final boolean sendNetworkBroadcast = mLastSystemClockTimeSendNetworkBroadcast;
+                final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast;
 
                 mCallback.acquireWakeLock();
                 try {
@@ -165,7 +204,7 @@
                     long actualTimeMillis = mCallback.systemClockMillis();
 
                     final String reason = "Automatic time detection enabled.";
-                    adjustAndSetDeviceSystemClock(mLastSystemClockTime, sendNetworkBroadcast,
+                    adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
                             elapsedRealtimeMillis, actualTimeMillis, reason);
                 } finally {
                     mCallback.releaseWakeLock();
@@ -174,22 +213,22 @@
         } else {
             // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
             // it should be in future.
-            mLastSystemClockTimeSet = null;
+            mLastAutoSystemClockTimeSet = null;
         }
     }
 
     @Override
     public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
         pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
-        pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
-        pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
-        pw.println("mLastSystemClockTimeSendNetworkBroadcast="
-                + mLastSystemClockTimeSendNetworkBroadcast);
+        pw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
+        pw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime);
+        pw.println("mLastAutoSystemClockTimeSendNetworkBroadcast="
+                + mLastAutoSystemClockTimeSendNetworkBroadcast);
     }
 
     private void adjustAndSetDeviceSystemClock(
             TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
-            long elapsedRealtimeMillis, long actualSystemClockMillis, String reason) {
+            long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) {
 
         // Adjust for the time that has elapsed since the signal was received.
         long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
@@ -203,20 +242,20 @@
                     + " system clock are close enough."
                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                     + " newTime=" + newTime
-                    + " reason=" + reason
+                    + " cause=" + cause
                     + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
                     + " absTimeDifference=" + absTimeDifference);
             return;
         }
 
         Slog.d(TAG, "Setting system clock using time=" + newTime
-                + " reason=" + reason
+                + " cause=" + cause
                 + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                 + " newTimeMillis=" + newSystemClockMillis);
         mCallback.setSystemClock(newSystemClockMillis);
 
         // CLOCK_PARANOIA : Record the last time this class set the system clock.
-        mLastSystemClockTimeSet = newTime;
+        mLastAutoSystemClockTimeSet = newTime;
 
         if (sendNetworkBroadcast) {
             // Send a broadcast that telephony code used to send after setting the clock.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index ee42279..0930975 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.timedetector.ITimeDetectorService;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -97,7 +98,7 @@
 
     @Override
     public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) {
-        enforceSetTimePermission();
+        enforceSuggestPhoneTimePermission();
         Objects.requireNonNull(timeSignal);
 
         long idToken = Binder.clearCallingIdentity();
@@ -110,10 +111,25 @@
         }
     }
 
+    @Override
+    public void suggestManualTime(@NonNull ManualTimeSuggestion timeSignal) {
+        enforceSuggestManualTimePermission();
+        Objects.requireNonNull(timeSignal);
+
+        long idToken = Binder.clearCallingIdentity();
+        try {
+            synchronized (mStrategyLock) {
+                mTimeDetectorStrategy.suggestManualTime(timeSignal);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(idToken);
+        }
+    }
+
     @VisibleForTesting
     public void handleAutoTimeDetectionToggle() {
         synchronized (mStrategyLock) {
-            final boolean timeDetectionEnabled = mCallback.isTimeDetectionEnabled();
+            final boolean timeDetectionEnabled = mCallback.isAutoTimeDetectionEnabled();
             mTimeDetectorStrategy.handleAutoTimeDetectionToggle(timeDetectionEnabled);
         }
     }
@@ -128,7 +144,11 @@
         }
     }
 
-    private void enforceSetTimePermission() {
+    private void enforceSuggestPhoneTimePermission() {
         mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
     }
-}
\ No newline at end of file
+
+    private void enforceSuggestManualTimePermission() {
+        mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 7c2a945..b60cebf 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.util.TimestampedValue;
@@ -47,7 +48,7 @@
         int systemClockUpdateThresholdMillis();
 
         /** Returns true if automatic time detection is enabled. */
-        boolean isTimeDetectionEnabled();
+        boolean isAutoTimeDetectionEnabled();
 
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
@@ -71,9 +72,12 @@
     /** Initialize the strategy. */
     void initialize(@NonNull Callback callback);
 
-    /** Process the suggested time. */
+    /** Process the suggested time from telephony sources. */
     void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion);
 
+    /** Process the suggested manually entered time. */
+    void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion);
+
     /** Handle the auto-time setting being toggled on or off. */
     void handleAutoTimeDetectionToggle(boolean enabled);
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 77b9e62..42d59d5 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -72,7 +72,7 @@
     }
 
     @Override
-    public boolean isTimeDetectionEnabled() {
+    public boolean isAutoTimeDetectionEnabled() {
         try {
             return Settings.Global.getInt(mContentResolver, Settings.Global.AUTO_TIME) != 0;
         } catch (Settings.SettingNotFoundException snfe) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b033492..51e4364 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -137,6 +137,8 @@
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.backup.IBackupManager;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.TimeDetector;
 import android.app.trust.TrustManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.compat.annotation.ChangeId;
@@ -1956,6 +1958,10 @@
             return mContext.getSystemService(AlarmManager.class);
         }
 
+        TimeDetector getTimeDetector() {
+            return mContext.getSystemService(TimeDetector.class);
+        }
+
         ConnectivityManager getConnectivityManager() {
             return mContext.getSystemService(ConnectivityManager.class);
         }
@@ -11022,7 +11028,10 @@
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
             return false;
         }
-        mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManager().setTime(millis));
+        ManualTimeSuggestion manualTimeSuggestion = TimeDetector.createManualTimeSuggestion(
+                millis, "DevicePolicyManagerService: setTime");
+        mInjector.binderWithCleanCallingIdentity(
+                () -> mInjector.getTimeDetector().suggestManualTime(manualTimeSuggestion));
         return true;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 64ea59d..f86bacf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -22,6 +22,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.backup.IBackupManager;
+import android.app.timedetector.TimeDetector;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.Intent;
@@ -229,6 +230,11 @@
         AlarmManager getAlarmManager() {return services.alarmManager;}
 
         @Override
+        TimeDetector getTimeDetector() {
+            return services.timeDetector;
+        }
+
+        @Override
         LockPatternUtils newLockPatternUtils() {
             return services.lockPatternUtils;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ed55aeb..b6ed22b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -66,6 +66,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.PasswordMetrics;
+import android.app.timedetector.ManualTimeSuggestion;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -3588,7 +3589,19 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
         dpm.setTime(admin1, 0);
-        verify(getServices().alarmManager).setTime(0);
+
+        BaseMatcher<ManualTimeSuggestion> hasZeroTime = new BaseMatcher<ManualTimeSuggestion>() {
+            @Override
+            public boolean matches(Object item) {
+                final ManualTimeSuggestion suggestion = (ManualTimeSuggestion) item;
+                return suggestion.getUtcTime().getValue() == 0;
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("ManualTimeSuggestion{utcTime.value=0}");
+            }
+        };
+        verify(getServices().timeDetector).suggestManualTime(argThat(hasZeroTime));
     }
 
     public void testSetTimeFailWithPO() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index bd513dc..1a67576 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -207,6 +207,8 @@
         switch (name) {
             case Context.ALARM_SERVICE:
                 return mMockSystemServices.alarmManager;
+            case Context.TIME_DETECTOR_SERVICE:
+                return mMockSystemServices.timeDetector;
             case Context.USER_SERVICE:
                 return mMockSystemServices.userManager;
             case Context.POWER_SERVICE:
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index b0d0303..c927364 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -31,6 +31,7 @@
 import android.app.IActivityTaskManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
+import android.app.timedetector.TimeDetector;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
@@ -111,6 +112,7 @@
     public final TelephonyManager telephonyManager;
     public final AccountManager accountManager;
     public final AlarmManager alarmManager;
+    public final TimeDetector timeDetector;
     public final KeyChain.KeyChainConnection keyChainConnection;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -152,6 +154,7 @@
         telephonyManager = mock(TelephonyManager.class);
         accountManager = mock(AccountManager.class);
         alarmManager = mock(AlarmManager.class);
+        timeDetector = mock(TimeDetector.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
 
         // Package manager is huge, so we use a partial mock instead.
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
index b49845a..317fd4d 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.icu.util.Calendar;
@@ -36,6 +37,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
+
 @RunWith(AndroidJUnit4.class)
 public class SimpleTimeZoneDetectorStrategyTest {
 
@@ -47,6 +50,8 @@
 
     private static final int ARBITRARY_PHONE_ID = 123456;
 
+    private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
+
     private Script mScript;
 
     @Before
@@ -55,7 +60,7 @@
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_timeDetectionEnabled() {
+    public void testSuggestPhoneTime_autoTimeEnabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(true);
@@ -67,7 +72,8 @@
 
         mScript.simulateTimePassing(clockIncrement)
                 .simulatePhoneTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis, true /* expectNetworkBroadcast */);
     }
 
     @Test
@@ -103,7 +109,8 @@
 
         // Send the first time signal. It should be used.
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // Now send another time signal, but one that is too similar to the last one and should be
         // ignored.
@@ -130,11 +137,12 @@
                 TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
 
         mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis3, true /* expectNetworkBroadcast */);
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_timeDetectionDisabled() {
+    public void testSuggestPhoneTime_autoTimeDisabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(false);
@@ -146,7 +154,7 @@
     }
 
     @Test
-    public void testSuggestPhoneTime_nitz_invalidNitzReferenceTimesIgnored() {
+    public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
         Scenario scenario = SCENARIO_1;
         final int systemClockUpdateThreshold = 2000;
         mScript.pokeFakeClocks(scenario)
@@ -161,7 +169,8 @@
         long expectedSystemClockMillis1 =
                 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // The UTC time increment should be larger than the system clock update threshold so we
         // know it shouldn't be ignored for other reasons.
@@ -197,7 +206,8 @@
         PhoneTimeSuggestion timeSuggestion4 =
                 createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
     }
 
     @Test
@@ -229,7 +239,8 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
 
         // Turn off auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
@@ -256,7 +267,99 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */);
+    }
+
+    @Test
+    public void testSuggestManualTime_autoTimeDisabled() {
+        Scenario scenario = SCENARIO_1;
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(false);
+
+        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
+        final int clockIncrement = 1000;
+        long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectSystemClockMillis, false /* expectNetworkBroadcast */);
+    }
+
+    @Test
+    public void testSuggestManualTime_retainsAutoSignal() {
+        Scenario scenario = SCENARIO_1;
+
+        // Configure the start state.
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(true);
+
+        // Simulate a phone suggestion.
+        PhoneTimeSuggestion phoneTimeSuggestion =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
+        final int clockIncrement = 1000;
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch to manual.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking();
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+
+        // Simulate a manual suggestion 1 day different from the auto suggestion.
+        long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
+        long expectedManualClockMillis = manualTimeMillis;
+        ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
+        mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(
+                        expectedManualClockMillis, false /* expectNetworkBroadcast */);
+
+        // Simulate the passage of time.
+        mScript.simulateTimePassing(clockIncrement);
+        expectedAutoClockMillis += clockIncrement;
+
+        // Switch back to auto.
+        mScript.simulateAutoTimeDetectionToggle();
+
+        mScript.verifySystemClockWasSetAndResetCallTracking(
+                        expectedAutoClockMillis, true /* expectNetworkBroadcast */);
+
+        // Switch back to manual - nothing should happen to the clock.
+        mScript.simulateAutoTimeDetectionToggle()
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
+    /**
+     * Manual suggestions should be ignored if auto time is enabled.
+     */
+    @Test
+    public void testSuggestManualTime_autoTimeEnabled() {
+        Scenario scenario = SCENARIO_1;
+        mScript.pokeFakeClocks(scenario)
+                .pokeTimeDetectionEnabled(true);
+
+        ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
+        final int clockIncrement = 1000;
+
+        mScript.simulateTimePassing(clockIncrement)
+                .simulateManualTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
     /**
@@ -280,7 +383,7 @@
         }
 
         @Override
-        public boolean isTimeDetectionEnabled() {
+        public boolean isAutoTimeDetectionEnabled() {
             return mTimeDetectionEnabled;
         }
 
@@ -426,8 +529,13 @@
             return this;
         }
 
+        Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
+            mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
+            return this;
+        }
+
         Script simulateAutoTimeDetectionToggle() {
-            boolean enabled = !mFakeCallback.isTimeDetectionEnabled();
+            boolean enabled = !mFakeCallback.isAutoTimeDetectionEnabled();
             mFakeCallback.pokeTimeDetectionEnabled(enabled);
             mSimpleTimeDetectorStrategy.handleAutoTimeDetectionToggle(enabled);
             return this;
@@ -445,9 +553,12 @@
             return this;
         }
 
-        Script verifySystemClockWasSetAndResetCallTracking(long expectSystemClockMillis) {
+        Script verifySystemClockWasSetAndResetCallTracking(
+                long expectSystemClockMillis, boolean expectNetworkBroadcast) {
             mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
-            mFakeCallback.verifyIntentWasBroadcast();
+            if (expectNetworkBroadcast) {
+                mFakeCallback.verifyIntentWasBroadcast();
+            }
             mFakeCallback.resetCallTracking();
             return this;
         }
@@ -486,6 +597,12 @@
             return createPhoneTimeSuggestion(phoneId, time);
         }
 
+        ManualTimeSuggestion createManualTimeSuggestionForActual() {
+            TimestampedValue<Long> time = new TimestampedValue<>(
+                    mInitialDeviceRealtimeMillis, mActualTimeMillis);
+            return new ManualTimeSuggestion(time);
+        }
+
         static class Builder {
 
             private long mInitialDeviceSystemClockMillis;
@@ -525,6 +642,12 @@
         return timeSuggestion;
     }
 
+    private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) {
+        TimestampedValue<Long> utcTime =
+                new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis);
+        return new ManualTimeSuggestion(utcTime);
+    }
+
     private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
             int second) {
         Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
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 cfd8a45..4efe771 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -90,6 +91,19 @@
     }
 
     @Test
+    public void testSuggestManualTime() {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
+        mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SET_TIME),
+                anyString());
+        mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
+    }
+
+    @Test
     public void testDump() {
         when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -102,13 +116,13 @@
 
     @Test
     public void testAutoTimeDetectionToggle() {
-        when(mMockCallback.isTimeDetectionEnabled()).thenReturn(true);
+        when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(true);
 
         mTimeDetectorService.handleAutoTimeDetectionToggle();
 
         mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(true);
 
-        when(mMockCallback.isTimeDetectionEnabled()).thenReturn(false);
+        when(mMockCallback.isAutoTimeDetectionEnabled()).thenReturn(false);
 
         mTimeDetectorService.handleAutoTimeDetectionToggle();
 
@@ -123,10 +137,16 @@
         return suggestion;
     }
 
+    private static ManualTimeSuggestion createManualTimeSuggestion() {
+        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+        return new ManualTimeSuggestion(timeValue);
+    }
+
     private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
 
         // Call tracking.
         private PhoneTimeSuggestion mLastPhoneSuggestion;
+        private ManualTimeSuggestion mLastManualSuggestion;
         private Boolean mLastAutoTimeDetectionToggle;
         private boolean mDumpCalled;
 
@@ -141,6 +161,12 @@
         }
 
         @Override
+        public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
+            resetCallTracking();
+            mLastManualSuggestion = timeSuggestion;
+        }
+
+        @Override
         public void handleAutoTimeDetectionToggle(boolean enabled) {
             resetCallTracking();
             mLastAutoTimeDetectionToggle = enabled;
@@ -154,12 +180,17 @@
 
         void resetCallTracking() {
             mLastPhoneSuggestion = null;
+            mLastManualSuggestion = null;
             mLastAutoTimeDetectionToggle = null;
             mDumpCalled = false;
         }
 
-        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSignal) {
-            assertEquals(expectedSignal, mLastPhoneSuggestion);
+        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastPhoneSuggestion);
+        }
+
+        public void verifySuggestManualTimeCalled(ManualTimeSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastManualSuggestion);
         }
 
         void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) {