Merge "Make NTP config configurable from cmd" am: 271dfbe8df am: 5c576e6d48 am: 24753d5b6f

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2007272

Change-Id: I0c4861e06ad9d91a8d5c2722cec442d391453464
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 40beab3..aebc5e8 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -33,6 +33,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Objects;
 import java.util.function.Supplier;
 
@@ -96,8 +99,8 @@
         @Override
         public String toString() {
             return "TimeResult{"
-                    + "mTimeMillis=" + mTimeMillis
-                    + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+                    + "mTimeMillis=" + Instant.ofEpochMilli(mTimeMillis)
+                    + ", mElapsedRealtimeMillis=" + Duration.ofMillis(mElapsedRealtimeMillis)
                     + ", mCertaintyMillis=" + mCertaintyMillis
                     + '}';
         }
@@ -131,6 +134,14 @@
         }
     };
 
+    /** An in-memory config override for use during tests. */
+    @Nullable
+    private String mHostnameForTests;
+
+    /** An in-memory config override for use during tests. */
+    @Nullable
+    private Duration mTimeoutForTests;
+
     // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
     // forceRefresh().
     private volatile TimeResult mTimeResult;
@@ -148,12 +159,23 @@
         return sSingleton;
     }
 
+    /**
+     * Overrides the NTP server config for tests. Passing {@code null} to a parameter clears the
+     * test value, i.e. so the normal value will be used next time.
+     */
+    public void setServerConfigForTests(@Nullable String hostname, @Nullable Duration timeout) {
+        synchronized (this) {
+            mHostnameForTests = hostname;
+            mTimeoutForTests = timeout;
+        }
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean forceRefresh() {
         synchronized (this) {
             NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
             if (connectionInfo == null) {
-                // missing server config, so no trusted time available
+                // missing server config, so no NTP time available
                 if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
                 return false;
             }
@@ -290,6 +312,14 @@
         int getTimeoutMillis() {
             return mTimeoutMillis;
         }
+
+        @Override
+        public String toString() {
+            return "NtpConnectionInfo{"
+                    + "mServer='" + mServer + '\''
+                    + ", mTimeoutMillis=" + mTimeoutMillis
+                    + '}';
+        }
     }
 
     @GuardedBy("this")
@@ -297,17 +327,41 @@
         final ContentResolver resolver = mContext.getContentResolver();
 
         final Resources res = mContext.getResources();
-        final String defaultServer = res.getString(
-                com.android.internal.R.string.config_ntpServer);
-        final int defaultTimeoutMillis = res.getInteger(
-                com.android.internal.R.integer.config_ntpTimeout);
 
-        final String secureServer = Settings.Global.getString(
-                resolver, Settings.Global.NTP_SERVER);
-        final int timeoutMillis = Settings.Global.getInt(
-                resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+        final String hostname;
+        if (mHostnameForTests != null) {
+            hostname = mHostnameForTests;
+        } else {
+            String serverGlobalSetting =
+                    Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);
+            if (serverGlobalSetting != null) {
+                hostname = serverGlobalSetting;
+            } else {
+                hostname = res.getString(com.android.internal.R.string.config_ntpServer);
+            }
+        }
 
-        final String server = secureServer != null ? secureServer : defaultServer;
-        return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+        final int timeoutMillis;
+        if (mTimeoutForTests != null) {
+            timeoutMillis = (int) mTimeoutForTests.toMillis();
+        } else {
+            int defaultTimeoutMillis =
+                    res.getInteger(com.android.internal.R.integer.config_ntpTimeout);
+            timeoutMillis = Settings.Global.getInt(
+                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+        }
+        return TextUtils.isEmpty(hostname) ? null : new NtpConnectionInfo(hostname, timeoutMillis);
+    }
+
+    /** Prints debug information. */
+    public void dump(PrintWriter pw) {
+        synchronized (this) {
+            pw.println("getNtpConnectionInfo()=" + getNtpConnectionInfo());
+            pw.println("mTimeResult=" + mTimeResult);
+            if (mTimeResult != null) {
+                pw.println("mTimeResult.getAgeMillis()="
+                        + Duration.ofMillis(mTimeResult.getAgeMillis()));
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index 1e534b7..186ff62 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.timedetector.NetworkTimeSuggestion;
@@ -45,12 +46,12 @@
 import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.NtpTrustedTime.TimeResult;
-import android.util.TimeUtils;
 
 import com.android.internal.util.DumpUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.Duration;
 
 /**
  * Monitors the network time. If looking up the network time fails for some reason, it tries a few
@@ -191,6 +192,19 @@
         return success;
     }
 
+    /**
+     * Overrides the NTP server config for tests. Passing {@code null} to a parameter clears the
+     * test value, i.e. so the normal value will be used next time.
+     */
+    void setServerConfigForTests(@Nullable String hostname, @Nullable Duration timeout) {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.SET_TIME, "set NTP server config for tests");
+
+        mLocalLog.log("Setting server config for tests: hostname=" + hostname
+                + ", timeout=" + timeout);
+        mTime.setServerConfigForTests(hostname, timeout);
+    }
+
     private void onPollNetworkTime(int event) {
         // If we don't have any default network, don't bother.
         if (mDefaultNetwork == null) return;
@@ -349,17 +363,14 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-        pw.print("PollingIntervalMs: ");
-        TimeUtils.formatDuration(mPollingIntervalMs, pw);
-        pw.print("\nPollingIntervalShorterMs: ");
-        TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
-        pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
-        pw.println("\nTryAgainCounter: " + mTryAgainCounter);
-        NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
-        pw.println("NTP cache result: " + ntpResult);
-        if (ntpResult != null) {
-            pw.println("NTP result age: " + ntpResult.getAgeMillis());
-        }
+        pw.println("mPollingIntervalMs=" + Duration.ofMillis(mPollingIntervalMs));
+        pw.println("mPollingIntervalShorterMs=" + Duration.ofMillis(mPollingIntervalShorterMs));
+        pw.println("mTryAgainTimesMax=" + mTryAgainTimesMax);
+        pw.println("mTryAgainCounter=" + mTryAgainCounter);
+        pw.println();
+        pw.println("NtpTrustedTime:");
+        mTime.dump(pw);
+        pw.println();
         pw.println("Local logs:");
         mLocalLog.dump(fd, pw, args);
         pw.println();
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
index dc93023..464af01 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceShellCommand.java
@@ -20,6 +20,7 @@
 import android.os.ShellCommand;
 
 import java.io.PrintWriter;
+import java.time.Duration;
 import java.util.Objects;
 
 /** Implements the shell command interface for {@link NetworkTimeUpdateService}. */
@@ -40,6 +41,13 @@
      */
     private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
 
+    /**
+     * A shell command that sets the NTP server config for tests. Config is cleared on reboot.
+     */
+    private static final String SHELL_COMMAND_SET_SERVER_CONFIG = "set_server_config";
+    private static final String SET_SERVER_CONFIG_HOSTNAME_ARG = "--hostname";
+    private static final String SET_SERVER_CONFIG_TIMEOUT_ARG = "--timeout_millis";
+
     @NonNull
     private final NetworkTimeUpdateService mNetworkTimeUpdateService;
 
@@ -58,6 +66,8 @@
                 return runClearTime();
             case SHELL_COMMAND_FORCE_REFRESH:
                 return runForceRefresh();
+            case SHELL_COMMAND_SET_SERVER_CONFIG:
+                return runSetServerConfig();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -75,6 +85,29 @@
         return 0;
     }
 
+    private int runSetServerConfig() {
+        String hostname = null;
+        Duration timeout = null;
+        String opt;
+        while ((opt = getNextArg()) != null) {
+            switch (opt) {
+                case SET_SERVER_CONFIG_HOSTNAME_ARG: {
+                    hostname = getNextArgRequired();
+                    break;
+                }
+                case SET_SERVER_CONFIG_TIMEOUT_ARG: {
+                    timeout = Duration.ofMillis(Integer.parseInt(getNextArgRequired()));
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("Unknown option: " + opt);
+                }
+            }
+        }
+        mNetworkTimeUpdateService.setServerConfigForTests(hostname, timeout);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -85,6 +118,12 @@
         pw.printf("    Clears the latest time.\n");
         pw.printf("  %s\n", SHELL_COMMAND_FORCE_REFRESH);
         pw.printf("    Refreshes the latest time. Prints whether it was successful.\n");
+        pw.printf("  %s\n", SHELL_COMMAND_SET_SERVER_CONFIG);
+        pw.printf("    Sets the NTP server config for tests. The config is not persisted.\n");
+        pw.printf("      Options: [%s <hostname>] [%s <millis>]\n",
+                SET_SERVER_CONFIG_HOSTNAME_ARG, SET_SERVER_CONFIG_TIMEOUT_ARG);
+        pw.printf("      Each key/value is optional and must be specified to override the\n");
+        pw.printf("      normal value, not specifying a key causes it to reset to the original.\n");
         pw.println();
     }
 }