Add APIs for configuring VPN automatic keepalive and IP versions

This change adds support for configuring the keepalive and IP version
overrides within IKEv2 VPNs, enabling power and performance optimization
on a per-network/per-carrier basis.

Bug: 259000745
Test: atest Ikev2VpnProfileTest VpnProfileTest
Change-Id: Ia2015d687b0c4b4195470680e6404c9f35679d3c
diff --git a/core/api/current.txt b/core/api/current.txt
index 2d2f390..14dbcbe 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26608,6 +26608,8 @@
     method @Nullable public java.security.cert.X509Certificate getUserCert();
     method @NonNull public String getUserIdentity();
     method @Nullable public String getUsername();
+    method public boolean isAutomaticIpVersionSelectionEnabled();
+    method public boolean isAutomaticNattKeepaliveTimerEnabled();
     method public boolean isBypassable();
     method public boolean isMetered();
   }
@@ -26620,6 +26622,8 @@
     method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey, @Nullable java.security.cert.X509Certificate);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate);
+    method @NonNull public android.net.Ikev2VpnProfile.Builder setAutomaticIpVersionSelectionEnabled(boolean);
+    method @NonNull public android.net.Ikev2VpnProfile.Builder setAutomaticNattKeepaliveTimerEnabled(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setLocalRoutesExcluded(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int);
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 10ce3bf..6475144 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -138,6 +138,8 @@
     private final int mMaxMtu; // Defaults in builder
     private final boolean mIsRestrictedToTestNetworks;
     @Nullable private final IkeTunnelConnectionParams mIkeTunConnParams;
+    private final boolean mAutomaticNattKeepaliveTimerEnabled;
+    private final boolean mAutomaticIpVersionSelectionEnabled;
 
     private Ikev2VpnProfile(
             int type,
@@ -157,7 +159,9 @@
             boolean restrictToTestNetworks,
             boolean excludeLocalRoutes,
             boolean requiresInternetValidation,
-            @Nullable IkeTunnelConnectionParams ikeTunConnParams) {
+            @Nullable IkeTunnelConnectionParams ikeTunConnParams,
+            boolean automaticNattKeepaliveTimerEnabled,
+            boolean automaticIpVersionSelectionEnabled) {
         super(type, excludeLocalRoutes, requiresInternetValidation);
 
         checkNotNull(allowedAlgorithms, MISSING_PARAM_MSG_TMPL, "Allowed Algorithms");
@@ -185,6 +189,8 @@
         mMaxMtu = maxMtu;
         mIsRestrictedToTestNetworks = restrictToTestNetworks;
         mIkeTunConnParams = ikeTunConnParams;
+        mAutomaticNattKeepaliveTimerEnabled = automaticNattKeepaliveTimerEnabled;
+        mAutomaticIpVersionSelectionEnabled = automaticIpVersionSelectionEnabled;
 
         validate();
     }
@@ -420,6 +426,16 @@
         return mIsRestrictedToTestNetworks;
     }
 
+    /** Returns whether automatic NAT-T keepalive timers are enabled. */
+    public boolean isAutomaticNattKeepaliveTimerEnabled() {
+        return mAutomaticNattKeepaliveTimerEnabled;
+    }
+
+    /** Returns whether automatic IP version selection is enabled. */
+    public boolean isAutomaticIpVersionSelectionEnabled() {
+        return mAutomaticIpVersionSelectionEnabled;
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(
@@ -440,7 +456,9 @@
                 mIsRestrictedToTestNetworks,
                 mExcludeLocalRoutes,
                 mRequiresInternetValidation,
-                mIkeTunConnParams);
+                mIkeTunConnParams,
+                mAutomaticNattKeepaliveTimerEnabled,
+                mAutomaticIpVersionSelectionEnabled);
     }
 
     @Override
@@ -467,7 +485,9 @@
                 && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks
                 && mExcludeLocalRoutes == other.mExcludeLocalRoutes
                 && mRequiresInternetValidation == other.mRequiresInternetValidation
-                && Objects.equals(mIkeTunConnParams, other.mIkeTunConnParams);
+                && Objects.equals(mIkeTunConnParams, other.mIkeTunConnParams)
+                && mAutomaticNattKeepaliveTimerEnabled == other.mAutomaticNattKeepaliveTimerEnabled
+                && mAutomaticIpVersionSelectionEnabled == other.mAutomaticIpVersionSelectionEnabled;
     }
 
     /**
@@ -482,7 +502,8 @@
     public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
         final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */,
                 mIsRestrictedToTestNetworks, mExcludeLocalRoutes, mRequiresInternetValidation,
-                mIkeTunConnParams);
+                mIkeTunConnParams, mAutomaticNattKeepaliveTimerEnabled,
+                mAutomaticIpVersionSelectionEnabled);
         profile.proxy = mProxyInfo;
         profile.isBypassable = mIsBypassable;
         profile.isMetered = mIsMetered;
@@ -603,6 +624,9 @@
         builder.setLocalRoutesExcluded(profile.excludeLocalRoutes && profile.isBypassable);
         builder.setRequiresInternetValidation(profile.requiresInternetValidation);
 
+        builder.setAutomaticNattKeepaliveTimerEnabled(profile.automaticNattKeepaliveTimerEnabled);
+        builder.setAutomaticIpVersionSelectionEnabled(profile.automaticIpVersionSelectionEnabled);
+
         return builder.build();
     }
 
@@ -773,6 +797,8 @@
         private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;
         private boolean mIsRestrictedToTestNetworks = false;
         private boolean mExcludeLocalRoutes = false;
+        private boolean mAutomaticNattKeepaliveTimerEnabled = false;
+        private boolean mAutomaticIpVersionSelectionEnabled = false;
         @Nullable private final IkeTunnelConnectionParams mIkeTunConnParams;
 
         /**
@@ -1080,6 +1106,34 @@
         }
 
         /**
+         * Sets the enabled state of the automatic NAT-T keepalive timers
+         *
+         * @param isEnabled {@code true} to enable automatic keepalive timers, based on internal
+         *     platform signals. Defaults to {@code false}.
+         * @return this {@link Builder} object to facilitate chaining of method calls
+         */
+        @NonNull
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        public Builder setAutomaticNattKeepaliveTimerEnabled(boolean isEnabled) {
+            mAutomaticNattKeepaliveTimerEnabled = isEnabled;
+            return this;
+        }
+
+        /**
+         * Sets the enabled state of the automatic IP version selection
+         *
+         * @param isEnabled {@code true} to enable automatic IP version selection, based on internal
+         *     platform signals. Defaults to {@code false}.
+         * @return this {@link Builder} object to facilitate chaining of method calls
+         */
+        @NonNull
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        public Builder setAutomaticIpVersionSelectionEnabled(boolean isEnabled) {
+            mAutomaticIpVersionSelectionEnabled = isEnabled;
+            return this;
+        }
+
+        /**
          * Sets whether the local traffic is exempted from the VPN.
          *
          * When this is set, the system will not use the VPN network when an app
@@ -1129,7 +1183,9 @@
                     mIsRestrictedToTestNetworks,
                     mExcludeLocalRoutes,
                     mRequiresInternetValidation,
-                    mIkeTunConnParams);
+                    mIkeTunConnParams,
+                    mAutomaticNattKeepaliveTimerEnabled,
+                    mAutomaticIpVersionSelectionEnabled);
         }
     }
 }
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index b334e91..545b261 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -152,6 +152,8 @@
     public final boolean excludeLocalRoutes;                     // 25
     public final boolean requiresInternetValidation;             // 26
     public final IkeTunnelConnectionParams ikeTunConnParams;     // 27
+    public final boolean automaticNattKeepaliveTimerEnabled;     // 28
+    public final boolean automaticIpVersionSelectionEnabled;     // 29
 
     // Helper fields.
     @UnsupportedAppUsage
@@ -167,11 +169,21 @@
 
     public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes,
             boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams) {
+        this(key, isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation,
+                ikeTunConnParams, false, false);
+    }
+
+    public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes,
+            boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams,
+            boolean automaticNattKeepaliveTimerEnabled,
+            boolean automaticIpVersionSelectionEnabled) {
         this.key = key;
         this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
         this.excludeLocalRoutes = excludeLocalRoutes;
         this.requiresInternetValidation = requiresInternetValidation;
         this.ikeTunConnParams = ikeTunConnParams;
+        this.automaticNattKeepaliveTimerEnabled = automaticNattKeepaliveTimerEnabled;
+        this.automaticIpVersionSelectionEnabled = automaticIpVersionSelectionEnabled;
     }
 
     @UnsupportedAppUsage
@@ -207,6 +219,8 @@
                 in.readParcelable(PersistableBundle.class.getClassLoader());
         ikeTunConnParams = (bundle == null) ? null
                 : TunnelConnectionParamsUtils.fromPersistableBundle(bundle);
+        automaticNattKeepaliveTimerEnabled = in.readBoolean();
+        automaticIpVersionSelectionEnabled = in.readBoolean();
     }
 
     /**
@@ -258,6 +272,8 @@
         out.writeBoolean(requiresInternetValidation);
         out.writeParcelable(ikeTunConnParams == null ? null
                 : TunnelConnectionParamsUtils.toPersistableBundle(ikeTunConnParams), flags);
+        out.writeBoolean(automaticNattKeepaliveTimerEnabled);
+        out.writeBoolean(automaticIpVersionSelectionEnabled);
     }
 
     /**
@@ -282,8 +298,9 @@
             // 27:                                            ...and requiresInternetValidation
             //     (26,27 can only be found on dogfood devices)
             // 28:                                            ...and ikeTunConnParams
+            // 29-30:                                         ...and automatic NATT/IP version
             if ((values.length < 14 || (values.length > 19 && values.length < 24)
-                    || values.length > 28)) {
+                    || (values.length > 28 && values.length < 30) || values.length > 30)) {
                 return null;
             }
 
@@ -322,8 +339,19 @@
                 tempIkeTunConnParams = null;
             }
 
+            final boolean automaticNattKeepaliveTimerEnabled;
+            final boolean automaticIpVersionSelectionEnabled;
+            if (values.length >= 30) {
+                automaticNattKeepaliveTimerEnabled = Boolean.parseBoolean(values[28]);
+                automaticIpVersionSelectionEnabled = Boolean.parseBoolean(values[29]);
+            } else {
+                automaticNattKeepaliveTimerEnabled = false;
+                automaticIpVersionSelectionEnabled = false;
+            }
+
             VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks,
-                    excludeLocalRoutes, requiresInternetValidation, tempIkeTunConnParams);
+                    excludeLocalRoutes, requiresInternetValidation, tempIkeTunConnParams,
+                    automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled);
             profile.name = values[0];
             profile.type = Integer.parseInt(values[1]);
             if (profile.type < 0 || profile.type > TYPE_MAX) {
@@ -447,6 +475,8 @@
         } else {
             builder.append(VALUE_DELIMITER).append("");
         }
+        builder.append(VALUE_DELIMITER).append(automaticNattKeepaliveTimerEnabled);
+        builder.append(VALUE_DELIMITER).append(automaticIpVersionSelectionEnabled);
 
         return builder.toString().getBytes(StandardCharsets.UTF_8);
     }
@@ -529,7 +559,8 @@
             l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
             proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
             isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation,
-            ikeTunConnParams);
+            ikeTunConnParams, automaticNattKeepaliveTimerEnabled,
+            automaticIpVersionSelectionEnabled);
     }
 
     /** Checks VPN profiles for interior equality. */
@@ -565,7 +596,9 @@
                 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks
                 && excludeLocalRoutes == other.excludeLocalRoutes
                 && requiresInternetValidation == other.requiresInternetValidation
-                && Objects.equals(ikeTunConnParams, other.ikeTunConnParams);
+                && Objects.equals(ikeTunConnParams, other.ikeTunConnParams)
+                && automaticNattKeepaliveTimerEnabled == other.automaticNattKeepaliveTimerEnabled
+                && automaticIpVersionSelectionEnabled == other.automaticIpVersionSelectionEnabled;
     }
 
     @NonNull