Merge changes If2201f39,Ia1c366c5

* changes:
  Stop calling Vpn#updateCapabilities in CS.
  Stop accessing VPNs in checkConnectivityDiagnosticsPermissions.
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
index d31218d..a17a498 100644
--- a/core/java/android/net/NetworkProvider.java
+++ b/core/java/android/net/NetworkProvider.java
@@ -51,13 +51,6 @@
     public static final int ID_NONE = -1;
 
     /**
-     * A hardcoded ID for NetworkAgents representing VPNs. These agents are not created by any
-     * provider, so they use this constant for clarity instead of NONE.
-     * @hide only used by ConnectivityService.
-     */
-    public static final int ID_VPN = -2;
-
-    /**
      * The first providerId value that will be allocated.
      * @hide only used by ConnectivityService.
      */
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 5e886a6..60a30a0 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -221,7 +221,7 @@
                     FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER;
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, prefix = { "STATE_" }, value = {
+    @IntDef(prefix = { "STATE_" }, value = {
             STATE_UNKNOWN_STATE,
             STATE_ENABLED,
             STATE_DISABLED,
@@ -231,7 +231,7 @@
     }
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, prefix = { "SOURCE_" }, value = {
+    @IntDef(prefix = { "SOURCE_" }, value = {
             SOURCE_UNKNOWN_SOURCE,
             SOURCE_APP_PROCESS,
             SOURCE_SYSTEM_SERVER
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 5867ef6..b1b6306 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -83,8 +83,7 @@
         boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY));
 
         // java.security.KeyStore
-        put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
-        put("Alg.Alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
+        put("KeyStore." + providerName, PACKAGE_NAME + ".AndroidKeyStoreSpi");
 
         // java.security.KeyPairGenerator
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bcd722e..f056117 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5131,7 +5131,7 @@
         }
     }
 
-    private void onUserStart(int userId) {
+    private void onUserStarted(int userId) {
         synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn != null) {
@@ -5146,7 +5146,7 @@
         }
     }
 
-    private void onUserStop(int userId) {
+    private void onUserStopped(int userId) {
         synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn == null) {
@@ -5257,9 +5257,9 @@
             if (userId == UserHandle.USER_NULL) return;
 
             if (Intent.ACTION_USER_STARTED.equals(action)) {
-                onUserStart(userId);
+                onUserStarted(userId);
             } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                onUserStop(userId);
+                onUserStopped(userId);
             } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                 onUserAdded(userId);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 8099f8f..3d1e709a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -7,3 +7,6 @@
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com, srnvs@google.com
+
+# Userspace reboot
+per-file UserspaceRebootLogger.java = ioffe@google.com, tomcherry@google.com
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
index 2403b63..89327b5 100644
--- a/services/core/java/com/android/server/UserspaceRebootLogger.java
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -59,7 +59,7 @@
      */
     public static void noteUserspaceRebootWasRequested() {
         if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "Userspace reboot is not supported.");
+            Slog.wtf(TAG, "noteUserspaceRebootWasRequested: Userspace reboot is not supported.");
             return;
         }
 
@@ -77,7 +77,7 @@
      */
     public static void noteUserspaceRebootSuccess() {
         if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "Userspace reboot is not supported.");
+            Slog.wtf(TAG, "noteUserspaceRebootSuccess: Userspace reboot is not supported.");
             return;
         }
 
@@ -88,11 +88,11 @@
     /**
      * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
      *
-     * <p>This call should only be made on devices supporting userspace reboot.
+     * <p>On devices that do not support userspace reboot this method will always return {@code
+     * false}.
      */
     public static boolean shouldLogUserspaceRebootEvent() {
         if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "Userspace reboot is not supported.");
             return false;
         }
 
@@ -110,7 +110,7 @@
      */
     public static void logEventAsync(boolean userUnlocked, Executor executor) {
         if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "Userspace reboot is not supported.");
+            Slog.wtf(TAG, "logEventAsync: Userspace reboot is not supported.");
             return;
         }
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 39b9294..73125c1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -122,7 +122,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.math.BigInteger;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -152,36 +151,13 @@
 public class Vpn {
     private static final String NETWORKTYPE = "VPN";
     private static final String TAG = "Vpn";
+    private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
     private static final boolean LOGD = true;
 
     // Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
     // the device idle allowlist during service launch and VPN bootstrap.
     private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;
 
-    // Settings for how much of the address space should be routed so that Vpn considers
-    // "most" of the address space is routed. This is used to determine whether this Vpn
-    // should be marked with the INTERNET capability.
-    private static final long MOST_IPV4_ADDRESSES_COUNT;
-    private static final BigInteger MOST_IPV6_ADDRESSES_COUNT;
-    static {
-        // 85% of the address space must be routed for Vpn to consider this VPN to provide
-        // INTERNET access.
-        final int howManyPercentIsMost = 85;
-
-        final long twoPower32 = 1L << 32;
-        MOST_IPV4_ADDRESSES_COUNT = twoPower32 * howManyPercentIsMost / 100;
-        final BigInteger twoPower128 = BigInteger.ONE.shiftLeft(128);
-        MOST_IPV6_ADDRESSES_COUNT = twoPower128
-                .multiply(BigInteger.valueOf(howManyPercentIsMost))
-                .divide(BigInteger.valueOf(100));
-    }
-    // How many routes to evaluate before bailing and declaring this Vpn should provide
-    // the INTERNET capability. This is necessary because computing the address space is
-    // O(n²) and this is running in the system service, so a limit is needed to alleviate
-    // the risk of attack.
-    // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
-    // is actually O(n²)+O(n²).
-    private static final int MAX_ROUTES_TO_EVALUATE = 150;
     private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
             Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;
     /**
@@ -198,6 +174,7 @@
     // automated reconnection
 
     private final Context mContext;
+    private final ConnectivityManager mConnectivityManager;
     // The context is for specific user which is created from mUserId
     private final Context mUserIdContext;
     @VisibleForTesting final Dependencies mDeps;
@@ -218,6 +195,7 @@
     private final INetworkManagementService mNetd;
     @VisibleForTesting
     protected VpnConfig mConfig;
+    private final NetworkProvider mNetworkProvider;
     @VisibleForTesting
     protected NetworkAgent mNetworkAgent;
     private final Looper mLooper;
@@ -401,6 +379,7 @@
             int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
         mDeps = deps;
         mNetd = netService;
@@ -419,6 +398,10 @@
             Log.wtf(TAG, "Problem registering observer", e);
         }
 
+        mNetworkProvider = new NetworkProvider(context, looper, VPN_PROVIDER_NAME_BASE + mUserId);
+        // This constructor is called in onUserStart and registers the provider. The provider
+        // will be unregistered in onUserStop.
+        mConnectivityManager.registerNetworkProvider(mNetworkProvider);
         mLegacyState = LegacyVpnInfo.STATE_DISCONNECTED;
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0 /* subtype */, NETWORKTYPE,
                 "" /* subtypeName */);
@@ -442,12 +425,39 @@
      * Update current state, dispatching event to listeners.
      */
     @VisibleForTesting
+    @GuardedBy("this")
     protected void updateState(DetailedState detailedState, String reason) {
         if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
         mLegacyState = LegacyVpnInfo.stateFromNetworkInfo(detailedState);
         mNetworkInfo.setDetailedState(detailedState, reason, null);
-        if (mNetworkAgent != null) {
-            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        // TODO : only accept transitions when the agent is in the correct state (non-null for
+        // CONNECTED, DISCONNECTED and FAILED, null for CONNECTED).
+        // This will require a way for tests to pretend the VPN is connected that's not
+        // calling this method with CONNECTED.
+        // It will also require audit of where the code calls this method with DISCONNECTED
+        // with a null agent, which it was doing historically to make sure the agent is
+        // disconnected as this was a no-op if the agent was null.
+        switch (detailedState) {
+            case CONNECTED:
+                if (null != mNetworkAgent) {
+                    mNetworkAgent.markConnected();
+                }
+                break;
+            case DISCONNECTED:
+            case FAILED:
+                if (null != mNetworkAgent) {
+                    mNetworkAgent.unregister();
+                    mNetworkAgent = null;
+                }
+                break;
+            case CONNECTING:
+                if (null != mNetworkAgent) {
+                    throw new IllegalStateException("VPN can only go to CONNECTING state when"
+                            + " the agent is null.");
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal state argument " + detailedState);
         }
         updateAlwaysOnNotification(detailedState);
     }
@@ -475,7 +485,7 @@
         final boolean isAlwaysMetered = mIsPackageTargetingAtLeastQ && mConfig.isMetered;
 
         applyUnderlyingCapabilities(
-                mContext.getSystemService(ConnectivityManager.class),
+                mConnectivityManager,
                 underlyingNetworks,
                 mNetworkCapabilities,
                 isAlwaysMetered);
@@ -485,10 +495,10 @@
 
     @VisibleForTesting
     public static void applyUnderlyingCapabilities(
-            ConnectivityManager cm,
-            Network[] underlyingNetworks,
-            NetworkCapabilities caps,
-            boolean isAlwaysMetered) {
+            @NonNull final ConnectivityManager cm,
+            @Nullable final Network[] underlyingNetworks,
+            @NonNull final NetworkCapabilities caps,
+            final boolean isAlwaysMetered) {
         int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN };
         int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
         int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
@@ -1015,7 +1025,7 @@
             }
             mConfig = null;
 
-            updateState(DetailedState.IDLE, "prepare");
+            updateState(DetailedState.DISCONNECTED, "prepare");
             setVpnForcedLocked(mLockdown);
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -1251,7 +1261,7 @@
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
         mLegacyState = LegacyVpnInfo.STATE_CONNECTING;
-        mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
+        updateState(DetailedState.CONNECTING, "agentConnect");
 
         NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
         networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
@@ -1269,20 +1279,23 @@
             mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
         }
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
-                    mNetworkInfo, mNetworkCapabilities, lp,
-                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
-                    NetworkProvider.ID_VPN) {
-                            @Override
-                            public void unwanted() {
-                                // We are user controlled, not driven by NetworkRequest.
-                            }
-                        };
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
+                mNetworkCapabilities, lp,
+                ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
+            @Override
+            public void unwanted() {
+                // We are user controlled, not driven by NetworkRequest.
+            }
+        };
+        Binder.withCleanCallingIdentity(() -> {
+            try {
+                mNetworkAgent.register();
+            } catch (final Exception e) {
+                // If register() throws, don't keep an unregistered agent.
+                mNetworkAgent = null;
+                throw e;
+            }
+        });
         mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
         mNetworkInfo.setIsAvailable(true);
@@ -1300,19 +1313,12 @@
 
     private void agentDisconnect(NetworkAgent networkAgent) {
         if (networkAgent != null) {
-            NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
-            networkInfo.setIsAvailable(false);
-            networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
-            networkAgent.sendNetworkInfo(networkInfo);
+            networkAgent.unregister();
         }
     }
 
     private void agentDisconnect() {
-        if (mNetworkInfo.isConnected()) {
-            mNetworkInfo.setIsAvailable(false);
-            updateState(DetailedState.DISCONNECTED, "agentDisconnect");
-            mNetworkAgent = null;
-        }
+        updateState(DetailedState.DISCONNECTED, "agentDisconnect");
     }
 
     /**
@@ -1401,6 +1407,8 @@
                     && updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) {
                 // Keep mNetworkAgent unchanged
             } else {
+                // Initialize the state for a new agent, while keeping the old one connected
+                // in case this new connection fails.
                 mNetworkAgent = null;
                 updateState(DetailedState.CONNECTING, "establish");
                 // Set up forwarding and DNS rules.
@@ -1636,6 +1644,9 @@
 
         // Quit any active connections
         agentDisconnect();
+
+        // The provider has been registered in the constructor, which is called in onUserStart.
+        mConnectivityManager.unregisterNetworkProvider(mNetworkProvider);
     }
 
     /**
@@ -2412,7 +2423,6 @@
             // When restricted to test networks, select any network with TRANSPORT_TEST. Since the
             // creator of the profile and the test network creator both have MANAGE_TEST_NETWORKS,
             // this is considered safe.
-            final ConnectivityManager cm = ConnectivityManager.from(mContext);
             final NetworkRequest req;
 
             if (mProfile.isRestrictedToTestNetworks()) {
@@ -2431,7 +2441,7 @@
                         .build();
             }
 
-            cm.requestNetwork(req, mNetworkCallback);
+            mConnectivityManager.requestNetwork(req, mNetworkCallback);
         }
 
         private boolean isActiveNetwork(@Nullable Network network) {
@@ -2718,8 +2728,7 @@
 
             resetIkeState();
 
-            final ConnectivityManager cm = ConnectivityManager.from(mContext);
-            cm.unregisterNetworkCallback(mNetworkCallback);
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
 
             mExecutor.shutdown();
         }
@@ -2800,13 +2809,12 @@
             mProfile = profile;
 
             if (!TextUtils.isEmpty(mOuterInterface)) {
-                final ConnectivityManager cm = ConnectivityManager.from(mContext);
-                for (Network network : cm.getAllNetworks()) {
-                    final LinkProperties lp = cm.getLinkProperties(network);
+                for (Network network : mConnectivityManager.getAllNetworks()) {
+                    final LinkProperties lp = mConnectivityManager.getLinkProperties(network);
                     if (lp != null && lp.getAllInterfaceNames().contains(mOuterInterface)) {
-                        final NetworkInfo networkInfo = cm.getNetworkInfo(network);
-                        if (networkInfo != null) {
-                            mOuterConnection.set(networkInfo.getType());
+                        final NetworkInfo netInfo = mConnectivityManager.getNetworkInfo(network);
+                        if (netInfo != null) {
+                            mOuterConnection.set(netInfo.getType());
                             break;
                         }
                     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index cef5cb6..4563a2d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -154,6 +154,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -261,7 +262,13 @@
 
         @Override
         public void onStart() {
-            AndroidKeyStoreProvider.install();
+            Optional<Boolean> keystore2_enabled =
+                    android.sysprop.Keystore2Properties.keystore2_enabled();
+            if (keystore2_enabled.isPresent() && keystore2_enabled.get()) {
+                android.security.keystore2.AndroidKeyStoreProvider.install();
+            } else {
+                AndroidKeyStoreProvider.install();
+            }
             mLockSettingsService = new LockSettingsService(getContext());
             publishBinderService("lock_settings", mLockSettingsService);
         }
@@ -542,7 +549,8 @@
 
         public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache() {
             try {
-                java.security.KeyStore ks = java.security.KeyStore.getInstance("AndroidKeyStore");
+                java.security.KeyStore ks = java.security.KeyStore.getInstance(
+                        SyntheticPasswordCrypto.androidKeystoreProviderName());
                 ks.load(null);
                 return new ManagedProfilePasswordCache(ks, getUserManager());
             } catch (Exception e) {
@@ -1284,7 +1292,8 @@
         byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
                 storedData.length);
         byte[] decryptionResult;
-        java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+        java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
+                SyntheticPasswordCrypto.androidKeystoreProviderName());
         keyStore.load(null);
         SecretKey decryptionKey = (SecretKey) keyStore.getKey(
                 LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId, null);
@@ -1743,7 +1752,8 @@
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
             keyGenerator.init(new SecureRandom());
             SecretKey secretKey = keyGenerator.generateKey();
-            java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+            java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
+                    SyntheticPasswordCrypto.androidKeystoreProviderName());
             keyStore.load(null);
             try {
                 keyStore.setEntry(
@@ -2299,7 +2309,8 @@
     private void removeKeystoreProfileKey(int targetUserId) {
         Slog.i(TAG, "Remove keystore profile key for user: " + targetUserId);
         try {
-            java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+            java.security.KeyStore keyStore = java.security.KeyStore.getInstance(
+                    SyntheticPasswordCrypto.androidKeystoreProviderName());
             keyStore.load(null);
             keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + targetUserId);
             keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + targetUserId);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 9246311..6d420a9 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -18,6 +18,7 @@
 
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
+import android.security.keystore2.AndroidKeyStoreProvider;
 import android.util.Slog;
 
 import java.io.ByteArrayOutputStream;
@@ -125,7 +126,7 @@
 
     public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
         try {
-            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
             keyStore.load(null);
 
             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
@@ -140,9 +141,24 @@
         }
     }
 
+    /**
+     * TODO This function redirects keystore access to the legacy keystore during a transitional
+     *      phase during which not all calling code has been adjusted to use Keystore 2.0.
+     *      This can be reverted to a constant of "AndroidKeyStore" when b/171305684 is complete.
+     *      The specific bug for this component is b/171305115.
+     */
+    static String androidKeystoreProviderName() {
+        if (AndroidKeyStoreProvider.isInstalled()) {
+            return "AndroidKeyStoreLegacy";
+        } else {
+            return "AndroidKeystore";
+        }
+
+    }
+
     public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
         try {
-            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
             keyStore.load(null);
 
             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
@@ -166,7 +182,7 @@
             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
             keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
             SecretKey secretKey = keyGenerator.generateKey();
-            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName());
             keyStore.load(null);
             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -196,7 +212,7 @@
     public static void destroyBlobKey(String keyAlias) {
         KeyStore keyStore;
         try {
-            keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore = KeyStore.getInstance(androidKeystoreProviderName());
             keyStore.load(null);
             keyStore.deleteEntry(keyAlias);
             Slog.i(TAG, "SP key deleted: " + keyAlias);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index c63f306..6dac6b1 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -125,7 +125,7 @@
     /** Internal method for handling the auto time setting being changed. */
     @VisibleForTesting
     public void handleAutoTimeDetectionChanged() {
-        mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
+        mHandler.post(mTimeDetectorStrategy::handleAutoTimeConfigChanged);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 13f0ab6..abd4c8c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -16,6 +16,7 @@
 
 package com.android.server.timedetector;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.timedetector.ManualTimeSuggestion;
@@ -24,6 +25,8 @@
 import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * The interface for the class that implements the time detection algorithm used by the
@@ -37,22 +40,41 @@
  */
 public interface TimeDetectorStrategy {
 
-    /** Process the suggested time from telephony sources. */
+    @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    int ORIGIN_TELEPHONY = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    int ORIGIN_MANUAL = 2;
+
+    /** Used when a time value originated from a network signal. */
+    @Origin
+    int ORIGIN_NETWORK = 3;
+
+    /** Processes the suggested time from telephony sources. */
     void suggestTelephonyTime(@NonNull TelephonyTimeSuggestion timeSuggestion);
 
     /**
-     * Process the suggested manually entered time. Returns {@code false} if the suggestion was
+     * Processes the suggested manually entered time. Returns {@code false} if the suggestion was
      * invalid, or the device configuration prevented the suggestion being used, {@code true} if the
      * suggestion was accepted. A suggestion that is valid but does not change the time because it
      * matches the current device time is considered accepted.
      */
     boolean suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion);
 
-    /** Process the suggested time from network sources. */
+    /** Processes the suggested time from network sources. */
     void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion);
 
-    /** Handle the auto-time setting being toggled on or off. */
-    void handleAutoTimeDetectionChanged();
+    /**
+     * Handles the auto-time configuration changing For example, when the auto-time setting is
+     * toggled on or off.
+     */
+    void handleAutoTimeConfigChanged();
 
     /** Dump debug information. */
     void dump(@NonNull PrintWriter pw, @Nullable String[] args);
@@ -67,4 +89,38 @@
         return (referenceClockMillisNow - timeValue.getReferenceTimeMillis())
                 + timeValue.getValue();
     }
+
+    /**
+     * Converts one of the {@code ORIGIN_} constants to a human readable string suitable for config
+     * and debug usage. Throws an {@link IllegalArgumentException} if the value is unrecognized.
+     */
+    static String originToString(@Origin int origin) {
+        switch (origin) {
+            case ORIGIN_MANUAL:
+                return "manual";
+            case ORIGIN_NETWORK:
+                return "network";
+            case ORIGIN_TELEPHONY:
+                return "telephony";
+            default:
+                throw new IllegalArgumentException("origin=" + origin);
+        }
+    }
+
+    /**
+     * Converts a human readable config string to one of the {@code ORIGIN_} constants.
+     * Throws an {@link IllegalArgumentException} if the value is unrecognized.
+     */
+    static @Origin int stringToOrigin(String originString) {
+        switch (originString) {
+            case "manual":
+                return ORIGIN_MANUAL;
+            case "network":
+                return ORIGIN_NETWORK;
+            case "telephony":
+                return ORIGIN_TELEPHONY;
+            default:
+                throw new IllegalArgumentException("originString=" + originString);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index e06fe92..5b6de05 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -16,16 +16,21 @@
 
 package com.android.server.timedetector;
 
+import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
+
 import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Slog;
 
+import java.time.Instant;
 import java.util.Objects;
 
 /**
@@ -38,6 +43,13 @@
     private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
 
     /**
+     * Time in the past. If automatic time suggestion is before this point, it's
+     * incorrect for sure.
+     */
+    private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
+            Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
+
+    /**
      * If a newly calculated system clock time and the current system clock time differs by this or
      * more the system clock will actually be updated. Used to prevent the system clock being set
      * for only minor differences.
@@ -48,6 +60,7 @@
     @NonNull private final ContentResolver mContentResolver;
     @NonNull private final PowerManager.WakeLock mWakeLock;
     @NonNull private final AlarmManager mAlarmManager;
+    @NonNull private final int[] mOriginPriorities;
 
     public TimeDetectorStrategyCallbackImpl(@NonNull Context context) {
         mContext = Objects.requireNonNull(context);
@@ -62,6 +75,15 @@
         mSystemClockUpdateThresholdMillis =
                 SystemProperties.getInt("ro.sys.time_detector_update_diff",
                         SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+
+        // TODO(b/172230856): Obtain these values from configuration.
+        String[] originStrings = { "telephony", "network" };
+        int[] origins = new int[originStrings.length];
+        for (int i = 0; i < originStrings.length; i++) {
+            int origin = stringToOrigin(originStrings[i]);
+            origins[i] = origin;
+        }
+        mOriginPriorities = origins;
     }
 
     @Override
@@ -79,6 +101,16 @@
     }
 
     @Override
+    public Instant autoTimeLowerBound() {
+        return TIME_LOWER_BOUND;
+    }
+
+    @Override
+    public int[] getAutoOriginPriorities() {
+        return mOriginPriorities;
+    }
+
+    @Override
     public void acquireWakeLock() {
         if (mWakeLock.isHeld()) {
             Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index d447a78..36a3ddd 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -16,7 +16,8 @@
 
 package com.android.server.timedetector;
 
-import android.annotation.IntDef;
+import static com.android.server.timedetector.TimeDetectorStrategy.originToString;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
@@ -34,8 +35,8 @@
 import com.android.server.timezonedetector.ReferenceWithHistory;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.Arrays;
 
 /**
  * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
@@ -63,22 +64,6 @@
     static final long MAX_UTC_TIME_AGE_MILLIS =
             TELEPHONY_BUCKET_COUNT * TELEPHONY_BUCKET_SIZE_MILLIS;
 
-    @IntDef({ ORIGIN_TELEPHONY, ORIGIN_MANUAL, ORIGIN_NETWORK })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Origin {}
-
-    /** Used when a time value originated from a telephony signal. */
-    @Origin
-    private static final int ORIGIN_TELEPHONY = 1;
-
-    /** Used when a time value originated from a user / manual settings. */
-    @Origin
-    private static final int ORIGIN_MANUAL = 2;
-
-    /** Used when a time value originated from a network signal. */
-    @Origin
-    private static final int ORIGIN_NETWORK = 3;
-
     /**
      * 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
@@ -143,6 +128,21 @@
         /** Returns true if automatic time detection is enabled. */
         boolean isAutoTimeDetectionEnabled();
 
+        /**
+         * Returns a lower bound for valid automatic times. It is guaranteed to be in the past,
+         * i.e. it is unrelated to the current system clock time.
+         * It holds no other meaning; it could be related to when the device system image was built,
+         * or could be updated by a mainline module.
+         */
+        @NonNull
+        Instant autoTimeLowerBound();
+
+        /**
+         * Returns the order to look at time suggestions when automatically detecting time.
+         * See {@code #ORIGIN_} constants
+         */
+        @Origin int[] getAutoOriginPriorities();
+
         /** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
         void acquireWakeLock();
 
@@ -177,7 +177,7 @@
 
     @Override
     public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion) {
-        if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
+        if (!validateAutoSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
             return;
         }
 
@@ -211,9 +211,12 @@
             return;
         }
 
-        // Perform validation / input filtering and record the validated suggestion against the
-        // slotIndex.
-        if (!validateAndStoreTelephonySuggestion(timeSuggestion)) {
+        if (!validateAutoSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
+            return;
+        }
+
+        // Perform input filtering and record the validated suggestion against the slotIndex.
+        if (!storeTelephonySuggestion(timeSuggestion)) {
             return;
         }
 
@@ -224,12 +227,12 @@
     }
 
     @Override
-    public synchronized void handleAutoTimeDetectionChanged() {
+    public synchronized void handleAutoTimeConfigChanged() {
         boolean enabled = mCallback.isAutoTimeDetectionEnabled();
         // When automatic time detection is enabled we update the system clock instantly if we can.
         // Conversely, when automatic time detection is disabled we leave the clock as it is.
         if (enabled) {
-            String reason = "Auto time zone detection setting enabled.";
+            String reason = "Auto time zone detection config changed.";
             doAutoTimeDetection(reason);
         } else {
             // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
@@ -272,14 +275,9 @@
     }
 
     @GuardedBy("this")
-    private boolean validateAndStoreTelephonySuggestion(
+    private boolean storeTelephonySuggestion(
             @NonNull TelephonyTimeSuggestion suggestion) {
         TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
-        if (!validateSuggestionTime(newUtcTime, suggestion)) {
-            // There's probably nothing useful we can do: elsewhere we assume that reference
-            // times are in the past so just stop here.
-            return false;
-        }
 
         int slotIndex = suggestion.getSlotIndex();
         TelephonyTimeSuggestion previousSuggestion = mSuggestionBySlotIndex.get(slotIndex);
@@ -330,6 +328,26 @@
         return true;
     }
 
+    private boolean validateAutoSuggestionTime(
+            @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion)  {
+        return validateSuggestionTime(newUtcTime, suggestion)
+                && validateSuggestionAgainstLowerBound(newUtcTime, suggestion);
+    }
+
+    private boolean validateSuggestionAgainstLowerBound(
+            @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
+        Instant lowerBound = mCallback.autoTimeLowerBound();
+
+        // Suggestion is definitely wrong if it comes before lower time bound.
+        if (lowerBound.isAfter(Instant.ofEpochMilli(newUtcTime.getValue()))) {
+            Slog.w(LOG_TAG, "Suggestion points to time before lower bound, skipping it. "
+                    + "suggestion=" + suggestion + ", lower bound=" + lowerBound);
+            return false;
+        }
+
+        return true;
+    }
+
     @GuardedBy("this")
     private void doAutoTimeDetection(@NonNull String detectionReason) {
         if (!mCallback.isAutoTimeDetectionEnabled()) {
@@ -337,33 +355,44 @@
             return;
         }
 
-        // Android devices currently prioritize any telephony over network signals. There are
-        // carrier compliance tests that would need to be changed before we could ignore NITZ or
-        // prefer NTP generally. This check is cheap on devices without telephony hardware.
-        TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion();
-        if (bestTelephonySuggestion != null) {
-            final TimestampedValue<Long> newUtcTime = bestTelephonySuggestion.getUtcTime();
-            String cause = "Found good telephony suggestion."
-                    + ", bestTelephonySuggestion=" + bestTelephonySuggestion
-                    + ", detectionReason=" + detectionReason;
-            setSystemClockIfRequired(ORIGIN_TELEPHONY, newUtcTime, cause);
-            return;
-        }
+        // Try the different origins one at a time.
+        int[] originPriorities = mCallback.getAutoOriginPriorities();
+        for (int origin : originPriorities) {
+            TimestampedValue<Long> newUtcTime = null;
+            String cause = null;
+            if (origin == ORIGIN_TELEPHONY) {
+                TelephonyTimeSuggestion bestTelephonySuggestion = findBestTelephonySuggestion();
+                if (bestTelephonySuggestion != null) {
+                    newUtcTime = bestTelephonySuggestion.getUtcTime();
+                    cause = "Found good telephony suggestion."
+                            + ", bestTelephonySuggestion=" + bestTelephonySuggestion
+                            + ", detectionReason=" + detectionReason;
+                }
+            } else if (origin == ORIGIN_NETWORK) {
+                NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion();
+                if (networkSuggestion != null) {
+                    newUtcTime = networkSuggestion.getUtcTime();
+                    cause = "Found good network suggestion."
+                            + ", networkSuggestion=" + networkSuggestion
+                            + ", detectionReason=" + detectionReason;
+                }
+            } else {
+                Slog.w(LOG_TAG, "Unknown or unsupported origin=" + origin
+                        + " in " + Arrays.toString(originPriorities)
+                        + ": Skipping");
+            }
 
-        // There is no good telephony suggestion, try network.
-        NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion();
-        if (networkSuggestion != null) {
-            final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime();
-            String cause = "Found good network suggestion."
-                    + ", networkSuggestion=" + networkSuggestion
-                    + ", detectionReason=" + detectionReason;
-            setSystemClockIfRequired(ORIGIN_NETWORK, newUtcTime, cause);
-            return;
+            // Update the system clock if a good suggestion has been found.
+            if (newUtcTime != null) {
+                setSystemClockIfRequired(origin, newUtcTime, cause);
+                return;
+            }
         }
 
         if (DBG) {
-            Slog.d(LOG_TAG, "Could not determine time: No best telephony or network suggestion."
-                    + " detectionReason=" + detectionReason);
+            Slog.d(LOG_TAG, "Could not determine time: No suggestion found in"
+                    + " originPriorities=" + Arrays.toString(originPriorities)
+                    + ", detectionReason=" + detectionReason);
         }
     }
 
@@ -448,7 +477,7 @@
         // Validate first.
         TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
         if (!validateSuggestionUtcTime(elapsedRealtimeMillis, utcTime)) {
-            Slog.w(LOG_TAG, "Existing suggestion found to be invalid "
+            Slog.w(LOG_TAG, "Existing suggestion found to be invalid"
                     + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
                     + ", timeSuggestion=" + timeSuggestion);
             return TELEPHONY_INVALID_SCORE;
@@ -497,7 +526,7 @@
             if (!mCallback.isAutoTimeDetectionEnabled()) {
                 if (DBG) {
                     Slog.d(LOG_TAG, "Auto time detection is not enabled."
-                            + " origin=" + origin
+                            + " origin=" + originToString(origin)
                             + ", time=" + time
                             + ", cause=" + cause);
                 }
@@ -507,7 +536,7 @@
             if (mCallback.isAutoTimeDetectionEnabled()) {
                 if (DBG) {
                     Slog.d(LOG_TAG, "Auto time detection is enabled."
-                            + " origin=" + origin
+                            + " origin=" + originToString(origin)
                             + ", time=" + time
                             + ", cause=" + cause);
                 }
@@ -529,7 +558,7 @@
 
     @GuardedBy("this")
     private boolean setSystemClockUnderWakeLock(
-            int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
+            @Origin int origin, @NonNull TimestampedValue<Long> newTime, @NonNull String cause) {
 
         long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
         boolean isOriginAutomatic = isOriginAutomatic(origin);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 39aa60b..0d2f57e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3246,7 +3246,7 @@
             int numTaskContainers = display.getTaskDisplayAreaCount();
             for (int tdaNdx = 0; tdaNdx < numTaskContainers; tdaNdx++) {
                 final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);
-                final int numStacks = display.getStackCount();
+                final int numStacks = taskDisplayArea.getStackCount();
                 for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
                     final ActivityStack stack = taskDisplayArea.getStackAt(stackNdx);
                     stack.finishVoiceTask(session);
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 02031bc..22addf9 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -242,7 +242,7 @@
         }
 
         @Override
-        public void handleAutoTimeDetectionChanged() {
+        public void handleAutoTimeConfigChanged() {
             mHandleAutoTimeDetectionChangedCalled = true;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 1be074d..c23fb80 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.timedetector;
 
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -25,32 +28,39 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
 import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.timedetector.TimeDetectorStrategy.Origin;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 
 @RunWith(AndroidJUnit4.class)
 public class TimeDetectorStrategyImplTest {
 
-    private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
+    private static final Instant TIME_LOWER_BOUND = createUtcTime(2009, 1, 1, 12, 0, 0);
+
+    private static final TimestampedValue<Instant> ARBITRARY_CLOCK_INITIALIZATION_INFO =
             new TimestampedValue<>(
                     123456789L /* realtimeClockMillis */,
-                    createUtcTime(2008, 5, 23, 12, 0, 0));
+                    createUtcTime(2010, 5, 23, 12, 0, 0));
+
+    // This is the traditional ordering for time detection on Android.
+    private static final @Origin int [] PROVIDERS_PRIORITY = { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
 
     /**
      * An arbitrary time, very different from the {@link #ARBITRARY_CLOCK_INITIALIZATION_INFO}
      * time. Can be used as the basis for time suggestions.
      */
-    private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
+    private static final Instant ARBITRARY_TEST_TIME = createUtcTime(2018, 1, 1, 12, 0, 0);
 
     private static final int ARBITRARY_SLOT_INDEX = 123456;
 
@@ -67,10 +77,10 @@
                 .pokeAutoTimeDetectionEnabled(true);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
-        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        Instant testTime = ARBITRARY_TEST_TIME;
 
         TelephonyTimeSuggestion timeSuggestion =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
         mScript.simulateTimePassing()
                 .simulateTelephonyTimeSuggestion(timeSuggestion);
 
@@ -106,9 +116,9 @@
         // Send the first time signal. It should be used.
         {
             TelephonyTimeSuggestion timeSuggestion1 =
-                    mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
+                    mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
 
-            // Increment the the device clocks to simulate the passage of time.
+            // Increment the device clocks to simulate the passage of time.
             mScript.simulateTimePassing(clockIncrementMillis);
 
             long expectedSystemClockMillis1 =
@@ -157,13 +167,13 @@
         // uses the lowest slotIndex when multiple telephony suggestions are available.
         int slotIndex1 = ARBITRARY_SLOT_INDEX;
         int slotIndex2 = ARBITRARY_SLOT_INDEX + 1;
-        long slotIndex1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
-        long slotIndex2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
+        Instant slotIndex1Time = ARBITRARY_TEST_TIME;
+        Instant slotIndex2Time = ARBITRARY_TEST_TIME.plus(Duration.ofDays(1));
 
         // Make a suggestion with slotIndex2.
         {
             TelephonyTimeSuggestion slotIndex2TimeSuggestion =
-                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
@@ -180,7 +190,7 @@
         // Now make a different suggestion with slotIndex1.
         {
             TelephonyTimeSuggestion slotIndex1TimeSuggestion =
-                    mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1TimeMillis);
+                    mScript.generateTelephonyTimeSuggestion(slotIndex1, slotIndex1Time);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
@@ -198,7 +208,7 @@
         // slotIndex1 suggestion will still "win".
         {
             TelephonyTimeSuggestion slotIndex2TimeSuggestion =
-                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
             mScript.simulateTimePassing();
 
             mScript.simulateTelephonyTimeSuggestion(slotIndex2TimeSuggestion)
@@ -213,7 +223,7 @@
         // is in an older "bucket".
         {
             TelephonyTimeSuggestion slotIndex2TimeSuggestion =
-                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2TimeMillis);
+                    mScript.generateTelephonyTimeSuggestion(slotIndex2, slotIndex2Time);
             mScript.simulateTimePassing();
 
             long expectedSystemClockMillis =
@@ -232,7 +242,7 @@
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
         TelephonyTimeSuggestion timeSuggestion =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME_MILLIS);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, ARBITRARY_TEST_TIME);
         mScript.simulateTimePassing()
                 .simulateTelephonyTimeSuggestion(timeSuggestion)
                 .verifySystemClockWasNotSetAndResetCallTracking()
@@ -246,11 +256,11 @@
                 .pokeThresholds(systemClockUpdateThreshold)
                 .pokeAutoTimeDetectionEnabled(true);
 
-        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        Instant testTime = ARBITRARY_TEST_TIME;
         int slotIndex = ARBITRARY_SLOT_INDEX;
 
         TelephonyTimeSuggestion timeSuggestion1 =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
         TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         // Initialize the strategy / device with a time set from a telephony suggestion.
@@ -300,6 +310,23 @@
     }
 
     @Test
+    public void telephonyTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        int slotIndex = ARBITRARY_SLOT_INDEX;
+        Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
+
+        TelephonyTimeSuggestion timeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(
+                        slotIndex, suggestedTime);
+
+        mScript.simulateTelephonyTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestTelephonySuggestion(slotIndex, null);
+    }
+
+    @Test
     public void testSuggestTelephonyTime_timeDetectionToggled() {
         final int clockIncrementMillis = 100;
         final int systemClockUpdateThreshold = 2000;
@@ -308,9 +335,9 @@
                 .pokeAutoTimeDetectionEnabled(false);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
-        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        Instant testTime = ARBITRARY_TEST_TIME;
         TelephonyTimeSuggestion timeSuggestion1 =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
         TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         // Simulate time passing.
@@ -366,9 +393,9 @@
                 .pokeAutoTimeDetectionEnabled(true);
 
         int slotIndex = ARBITRARY_SLOT_INDEX;
-        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        Instant testTime = ARBITRARY_TEST_TIME;
         TelephonyTimeSuggestion telephonySuggestion =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
 
         mScript.simulateTimePassing();
 
@@ -397,7 +424,7 @@
                 .pokeAutoTimeDetectionEnabled(false);
 
         ManualTimeSuggestion timeSuggestion =
-                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
 
         mScript.simulateTimePassing();
 
@@ -416,9 +443,9 @@
         int slotIndex = ARBITRARY_SLOT_INDEX;
 
         // Simulate a telephony suggestion.
-        long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+        Instant testTime = ARBITRARY_TEST_TIME;
         TelephonyTimeSuggestion telephonyTimeSuggestion =
-                mScript.generateTelephonyTimeSuggestion(slotIndex, testTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(slotIndex, testTime);
 
         // Simulate the passage of time.
         mScript.simulateTimePassing();
@@ -441,9 +468,9 @@
         mScript.simulateTimePassing();
 
         // Simulate a manual suggestion 1 day different from the auto suggestion.
-        long manualTimeMillis = testTimeMillis + Duration.ofDays(1).toMillis();
+        Instant manualTime = testTime.plus(Duration.ofDays(1));
         ManualTimeSuggestion manualTimeSuggestion =
-                mScript.generateManualTimeSuggestion(manualTimeMillis);
+                mScript.generateManualTimeSuggestion(manualTime);
         mScript.simulateTimePassing();
 
         long expectedManualClockMillis =
@@ -469,16 +496,13 @@
                 .assertLatestTelephonySuggestion(slotIndex, telephonyTimeSuggestion);
     }
 
-    /**
-     * Manual suggestions should be ignored if auto time is enabled.
-     */
     @Test
-    public void testSuggestManualTime_autoTimeEnabled() {
+    public void manualTimeSuggestion_isIgnored_whenAutoTimeEnabled() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
         ManualTimeSuggestion timeSuggestion =
-                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+                mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME);
 
         mScript.simulateTimePassing()
                 .simulateManualTimeSuggestion(timeSuggestion, false /* expectedResult */)
@@ -486,12 +510,25 @@
     }
 
     @Test
+    public void manualTimeSuggestion_ignoresTimeLowerBound() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(false);
+        Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
+
+        ManualTimeSuggestion timeSuggestion =
+                mScript.generateManualTimeSuggestion(suggestedTime);
+
+        mScript.simulateManualTimeSuggestion(timeSuggestion, true /* expectedResult */)
+                .verifySystemClockWasSetAndResetCallTracking(suggestedTime.toEpochMilli());
+    }
+
+    @Test
     public void testSuggestNetworkTime_autoTimeEnabled() {
         mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
                 .pokeAutoTimeDetectionEnabled(true);
 
         NetworkTimeSuggestion timeSuggestion =
-                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
 
         mScript.simulateTimePassing();
 
@@ -507,7 +544,7 @@
                 .pokeAutoTimeDetectionEnabled(false);
 
         NetworkTimeSuggestion timeSuggestion =
-                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
 
         mScript.simulateTimePassing()
                 .simulateNetworkTimeSuggestion(timeSuggestion)
@@ -520,16 +557,16 @@
                 .pokeAutoTimeDetectionEnabled(true);
 
         // Three obviously different times that could not be mistaken for each other.
-        long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS;
-        long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis();
-        long telephonyTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis();
+        Instant networkTime1 = ARBITRARY_TEST_TIME;
+        Instant networkTime2 = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+        Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(60));
         // A small increment used to simulate the passage of time, but not enough to interfere with
         // macro-level time changes associated with suggestion age.
         final long smallTimeIncrementMillis = 101;
 
         // A network suggestion is made. It should be used because there is no telephony suggestion.
         NetworkTimeSuggestion networkTimeSuggestion1 =
-                mScript.generateNetworkTimeSuggestion(networkTimeMillis1);
+                mScript.generateNetworkTimeSuggestion(networkTime1);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(
@@ -548,7 +585,7 @@
         // Now a telephony suggestion is made. Telephony suggestions are prioritized over network
         // suggestions so it should "win".
         TelephonyTimeSuggestion telephonyTimeSuggestion =
-                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeMillis);
+                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
@@ -568,7 +605,7 @@
         // Now another network suggestion is made. Telephony suggestions are prioritized over
         // network suggestions so the latest telephony suggestion should still "win".
         NetworkTimeSuggestion networkTimeSuggestion2 =
-                mScript.generateNetworkTimeSuggestion(networkTimeMillis2);
+                mScript.generateNetworkTimeSuggestion(networkTime2);
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateNetworkTimeSuggestion(networkTimeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking();
@@ -612,6 +649,87 @@
         assertNull(mScript.peekBestTelephonySuggestion());
     }
 
+    @Test
+    public void networkTimeSuggestion_ignoredWhenReferencedTimeIsInThePast() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        Instant suggestedTime = TIME_LOWER_BOUND.minus(Duration.ofDays(1));
+        NetworkTimeSuggestion timeSuggestion = mScript
+                .generateNetworkTimeSuggestion(suggestedTime);
+
+        mScript.simulateNetworkTimeSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking()
+                .assertLatestNetworkSuggestion(null);
+    }
+
+    @Test
+    public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_lowerPriorityComesFirst() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        Instant networkTime = ARBITRARY_TEST_TIME;
+        Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+
+        NetworkTimeSuggestion networkTimeSuggestion =
+                mScript.generateNetworkTimeSuggestion(networkTime);
+        TelephonyTimeSuggestion telephonyTimeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
+
+        mScript.simulateNetworkTimeSuggestion(networkTimeSuggestion)
+                .simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
+                .assertLatestNetworkSuggestion(networkTimeSuggestion)
+                .assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
+    }
+
+    @Test
+    public void whenAllTimeSuggestionsAreAvailable_higherPriorityWins_higherPriorityComesFirst() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        Instant networkTime = ARBITRARY_TEST_TIME;
+        Instant telephonyTime = ARBITRARY_TEST_TIME.plus(Duration.ofDays(30));
+
+        NetworkTimeSuggestion networkTimeSuggestion =
+                mScript.generateNetworkTimeSuggestion(networkTime);
+        TelephonyTimeSuggestion telephonyTimeSuggestion =
+                mScript.generateTelephonyTimeSuggestion(ARBITRARY_SLOT_INDEX, telephonyTime);
+
+        mScript.simulateTelephonyTimeSuggestion(telephonyTimeSuggestion)
+                .simulateNetworkTimeSuggestion(networkTimeSuggestion)
+                .assertLatestNetworkSuggestion(networkTimeSuggestion)
+                .assertLatestTelephonySuggestion(ARBITRARY_SLOT_INDEX, telephonyTimeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(telephonyTime.toEpochMilli());
+    }
+
+    @Test
+    public void whenHighestPrioritySuggestionIsNotAvailable_fallbacksToNext() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true);
+
+        NetworkTimeSuggestion timeSuggestion =
+                mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME);
+
+        mScript.simulateNetworkTimeSuggestion(timeSuggestion)
+                .assertLatestNetworkSuggestion(timeSuggestion)
+                .verifySystemClockWasSetAndResetCallTracking(ARBITRARY_TEST_TIME.toEpochMilli());
+    }
+
+    @Test
+    public void suggestionsFromSourceNotListedInPrioritiesList_areIgnored() {
+        mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+                .pokeAutoTimeDetectionEnabled(true)
+                .pokeAutoOriginPriorities(new int[]{ORIGIN_TELEPHONY});
+
+        NetworkTimeSuggestion timeSuggestion = mScript.generateNetworkTimeSuggestion(
+                ARBITRARY_TEST_TIME);
+
+        mScript.simulateNetworkTimeSuggestion(timeSuggestion)
+                .assertLatestNetworkSuggestion(timeSuggestion)
+                .verifySystemClockWasNotSetAndResetCallTracking();
+    }
+
     /**
      * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
      * like the real thing should, it also asserts preconditions.
@@ -622,6 +740,7 @@
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockUpdateThresholdMillis = 2000;
+        private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
@@ -637,6 +756,16 @@
         }
 
         @Override
+        public Instant autoTimeLowerBound() {
+            return TIME_LOWER_BOUND;
+        }
+
+        @Override
+        public int[] getAutoOriginPriorities() {
+            return mAutoOriginPriorities;
+        }
+
+        @Override
         public void acquireWakeLock() {
             if (mWakeLockAcquired) {
                 fail("Wake lock already acquired");
@@ -685,6 +814,10 @@
             mAutoTimeDetectionEnabled = enabled;
         }
 
+        void pokeAutoOriginPriorities(@Origin int[] autoOriginPriorities) {
+            mAutoOriginPriorities = autoOriginPriorities;
+        }
+
         long peekElapsedRealtimeMillis() {
             return mElapsedRealtimeMillis;
         }
@@ -703,7 +836,10 @@
         }
 
         void verifySystemClockNotSet() {
-            assertFalse(mSystemClockWasSet);
+            assertFalse(
+                    String.format("System clock was manipulated and set to %s(=%s)",
+                            Instant.ofEpochMilli(mSystemClockMillis), mSystemClockMillis),
+                    mSystemClockWasSet);
         }
 
         void verifySystemClockWasSet(long expectedSystemClockMillis) {
@@ -739,9 +875,9 @@
             return this;
         }
 
-        Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
+        Script pokeFakeClocks(TimestampedValue<Instant> timeInfo) {
             mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
-            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
+            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
             return this;
         }
 
@@ -750,6 +886,11 @@
             return this;
         }
 
+        Script pokeAutoOriginPriorities(@Origin int[] autoOriginPriorites) {
+            mFakeCallback.pokeAutoOriginPriorities(autoOriginPriorites);
+            return this;
+        }
+
         long peekElapsedRealtimeMillis() {
             return mFakeCallback.peekElapsedRealtimeMillis();
         }
@@ -765,7 +906,13 @@
 
         Script simulateManualTimeSuggestion(
                 ManualTimeSuggestion timeSuggestion, boolean expectedResult) {
-            assertEquals(expectedResult, mTimeDetectorStrategy.suggestManualTime(timeSuggestion));
+            String errorMessage = expectedResult
+                    ? "Manual time suggestion was ignored, but expected to be accepted."
+                    : "Manual time suggestion was accepted, but expected to be ignored.";
+            assertEquals(
+                    errorMessage,
+                    expectedResult,
+                    mTimeDetectorStrategy.suggestManualTime(timeSuggestion));
             return this;
         }
 
@@ -776,7 +923,7 @@
 
         Script simulateAutoTimeDetectionToggle() {
             mFakeCallback.simulateAutoTimeZoneDetectionToggle();
-            mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
+            mTimeDetectorStrategy.handleAutoTimeConfigChanged();
             return this;
         }
 
@@ -808,7 +955,10 @@
          * White box test info: Asserts the latest suggestion for the slotIndex is as expected.
          */
         Script assertLatestTelephonySuggestion(int slotIndex, TelephonyTimeSuggestion expected) {
-            assertEquals(expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
+            assertEquals(
+                    "Expected to see " + expected + " at slotIndex=" + slotIndex + ", but got "
+                            + mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex),
+                    expected, mTimeDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
             return this;
         }
 
@@ -840,9 +990,11 @@
          * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
          * reference time.
          */
-        ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) {
+        ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
-                    new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
+                    new TimestampedValue<>(
+                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            suggestedTime.toEpochMilli());
             return new ManualTimeSuggestion(utcTime);
         }
 
@@ -850,21 +1002,33 @@
          * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
          * the reference time.
          */
-        TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, Long timeMillis) {
-            TimestampedValue<Long> time = null;
-            if (timeMillis != null) {
-                time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
-            }
+        TelephonyTimeSuggestion generateTelephonyTimeSuggestion(int slotIndex, long timeMillis) {
+            TimestampedValue<Long> time =
+                    new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
             return createTelephonyTimeSuggestion(slotIndex, time);
         }
 
         /**
+         * Generates a {@link TelephonyTimeSuggestion} using the current elapsed realtime clock for
+         * the reference time.
+         */
+        TelephonyTimeSuggestion generateTelephonyTimeSuggestion(
+                int slotIndex, Instant suggestedTime) {
+            if (suggestedTime == null) {
+                return createTelephonyTimeSuggestion(slotIndex, null);
+            }
+            return generateTelephonyTimeSuggestion(slotIndex, suggestedTime.toEpochMilli());
+        }
+
+        /**
          * Generates a NetworkTimeSuggestion using the current elapsed realtime clock for the
          * reference time.
          */
-        NetworkTimeSuggestion generateNetworkTimeSuggestion(long timeMillis) {
+        NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
-                    new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
+                    new TimestampedValue<>(
+                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            suggestedTime.toEpochMilli());
             return new NetworkTimeSuggestion(utcTime);
         }
 
@@ -884,11 +1048,9 @@
                 .build();
     }
 
-    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
-            int second) {
-        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
-        cal.clear();
-        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
-        return cal.getTimeInMillis();
+    private static Instant createUtcTime(int year, int monthInYear, int day, int hourOfDay,
+            int minute, int second) {
+        return LocalDateTime.of(year, monthInYear, day, hourOfDay, minute, second)
+                .toInstant(ZoneOffset.UTC);
     }
 }
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index b77ed6a..c9161b6 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -22,11 +22,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Build;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,6 +37,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@IgnoreUpTo(Build.VERSION_CODES.R)
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class OemNetworkPreferencesTest {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c54190a..c917e66 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1091,6 +1091,10 @@
             mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
+            verify(mNetworkManagementService, times(1))
+                    .addVpnUidRanges(eq(mMockVpn.getNetId()), eq(uids.toArray(new UidRange[0])));
+            verify(mNetworkManagementService, never())
+                    .removeVpnUidRanges(eq(mMockVpn.getNetId()), any());
             mAgentRegistered = true;
             mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
             mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
@@ -6902,8 +6906,8 @@
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
         mMockVpn.establish(lp, VPN_UID, vpnRange);
 
-        // Connected VPN should have interface rules set up. There are two expected invocations,
-        // one during VPN uid update, one during VPN LinkProperties update
+        // A connected VPN should have interface rules set up. There are two expected invocations,
+        // one during the VPN initial connection, one during the VPN LinkProperties update.
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
         verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index a553b58..e1e0efa 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -41,6 +41,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
@@ -86,10 +87,10 @@
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
-import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.Credentials;
 import android.security.KeyStore;
@@ -100,6 +101,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.server.IpSecService;
@@ -223,6 +225,8 @@
                 .thenReturn(mNotificationManager);
         when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE)))
                 .thenReturn(mConnectivityManager);
+        when(mContext.getSystemServiceName(eq(ConnectivityManager.class)))
+                .thenReturn(Context.CONNECTIVITY_SERVICE);
         when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager);
         when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
                 .thenReturn(Resources.getSystem().getString(
@@ -589,7 +593,7 @@
     }
 
     @Test
-    public void testNotificationShownForAlwaysOnApp() {
+    public void testNotificationShownForAlwaysOnApp() throws Exception {
         final UserHandle userHandle = UserHandle.of(primaryUser.id);
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
@@ -619,7 +623,6 @@
 
     @Test
     public void testCapabilities() {
-        final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         final Network mobile = new Network(1);
@@ -1037,7 +1040,7 @@
         when(exception.getErrorType())
                 .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
 
-        final Vpn vpn = startLegacyVpn(mVpnProfile);
+        final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile));
         final NetworkCallback cb = triggerOnAvailableAndGetCallback();
 
         // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
@@ -1048,20 +1051,20 @@
         ikeCb.onClosedExceptionally(exception);
 
         verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
-        assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+        assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
     }
 
     @Test
     public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
         when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
                 .thenThrow(new IllegalArgumentException());
-        final Vpn vpn = startLegacyVpn(mVpnProfile);
+        final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), mVpnProfile);
         final NetworkCallback cb = triggerOnAvailableAndGetCallback();
 
         // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
         // state
         verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
-        assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+        assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
     }
 
     private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
@@ -1100,8 +1103,7 @@
         // a subsequent CL.
     }
 
-    public Vpn startLegacyVpn(final VpnProfile vpnProfile) throws Exception {
-        final Vpn vpn = createVpn(primaryUser.id);
+    private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
         setMockedUsers(primaryUser);
 
         // Dummy egress interface
@@ -1118,7 +1120,7 @@
 
     @Test
     public void testStartPlatformVpn() throws Exception {
-        startLegacyVpn(mVpnProfile);
+        startLegacyVpn(createVpn(primaryUser.id), mVpnProfile);
         // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
         // a subsequent patch.
     }
@@ -1153,7 +1155,7 @@
                     legacyRunnerReady.open();
                     return new Network(102);
                 });
-        final Vpn vpn = startLegacyVpn(profile);
+        final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile);
         final TestDeps deps = (TestDeps) vpn.mDeps;
         try {
             // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
@@ -1287,8 +1289,13 @@
         doReturn(UserHandle.of(userId)).when(asUserContext).getUser();
         when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
                 .thenReturn(asUserContext);
-        return new Vpn(Looper.myLooper(), mContext, new TestDeps(), mNetService,
+        final TestLooper testLooper = new TestLooper();
+        final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
                 userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
+        verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
+                provider -> provider.getName().contains("VpnNetworkProvider")
+        ));
+        return vpn;
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {