Expose vpn bypassability in VpnTransportInfo

Bug: 256775913
Test: m framework-connectivity.stubs.source.system-update-current-api
Test: m lint-check
Test: atest FrameworksNetTests
Change-Id: I6aaad7b4372aa4f91df4bed89caea674031a6367
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index a2a1ac0..988947a 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -228,13 +228,9 @@
   }
 
   public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
-    ctor public VpnTransportInfo(int, @Nullable String);
-    method public int describeContents();
+    ctor @Deprecated public VpnTransportInfo(int, @Nullable String);
     method @Nullable public String getSessionId();
-    method public int getType();
     method @NonNull public android.net.VpnTransportInfo makeCopy(long);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
   }
 
 }
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 8b35197..c7872a0 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -511,6 +511,15 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR;
   }
 
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor public VpnTransportInfo(int, @Nullable String, boolean);
+    method public int describeContents();
+    method public boolean getBypassable();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+  }
+
 }
 
 package android.net.apf {
diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java
index 4071c9a..ebad477 100644
--- a/framework/src/android/net/VpnTransportInfo.java
+++ b/framework/src/android/net/VpnTransportInfo.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
 
 import android.annotation.NonNull;
@@ -27,6 +28,10 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import androidx.annotation.RequiresApi;
+
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.Objects;
 
 /**
@@ -37,7 +42,7 @@
  *
  * @hide
  */
-@SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = PRIVILEGED_APPS)
 public final class VpnTransportInfo implements TransportInfo, Parcelable {
     /** Type of this VPN. */
     private final int mType;
@@ -45,6 +50,13 @@
     @Nullable
     private final String mSessionId;
 
+    private final boolean mBypassable;
+
+    // TODO: Refer to Build.VERSION_CODES when it's available in every branch.
+    private static final int UPSIDE_DOWN_CAKE = 34;
+
+    /** @hide */
+    @SystemApi(client = MODULE_LIBRARIES)
     @Override
     public @RedactionType long getApplicableRedactions() {
         return REDACT_FOR_NETWORK_SETTINGS;
@@ -52,21 +64,58 @@
 
     /**
      * Create a copy of a {@link VpnTransportInfo} with the sessionId redacted if necessary.
+     * @hide
      */
     @NonNull
+    @SystemApi(client = MODULE_LIBRARIES)
     public VpnTransportInfo makeCopy(@RedactionType long redactions) {
         return new VpnTransportInfo(mType,
-            ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : mSessionId);
+            ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : mSessionId, mBypassable);
     }
 
+    /**
+     * @deprecated please use {@link VpnTransportInfo(int,String,boolean)} instead.
+     * @hide
+     */
+    @Deprecated
+    @SystemApi(client = MODULE_LIBRARIES)
     public VpnTransportInfo(int type, @Nullable String sessionId) {
+        // When the module runs on older SDKs, |bypassable| will always be false since the old Vpn
+        // code will call this constructor. For Settings VPNs, this is always correct as they are
+        // never bypassable. For VpnManager and VpnService types, this may be wrong since both of
+        // them have a choice. However, on these SDKs VpnTransportInfo#getBypassable is not
+        // available anyway, so this should be harmless. False is a better choice than true here
+        // regardless because it is the default value for both VpnManager and VpnService if the app
+        // does not do anything about it.
+        this(type, sessionId, false /* bypassable */);
+    }
+
+    public VpnTransportInfo(int type, @Nullable String sessionId, boolean bypassable) {
         this.mType = type;
         this.mSessionId = sessionId;
+        this.mBypassable = bypassable;
+    }
+
+    /**
+     * Returns whether the VPN is allowing bypass.
+     *
+     * This method is not supported in SDK below U, and will throw
+     * {@code UnsupportedOperationException} if called.
+     */
+    @RequiresApi(UPSIDE_DOWN_CAKE)
+    public boolean getBypassable() {
+        if (!SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("Not supported before U");
+        }
+
+        return mBypassable;
     }
 
     /**
      * Returns the session Id of this VpnTransportInfo.
+     * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     @Nullable
     public String getSessionId() {
         return mSessionId;
@@ -84,17 +133,19 @@
         if (!(o instanceof VpnTransportInfo)) return false;
 
         VpnTransportInfo that = (VpnTransportInfo) o;
-        return (this.mType == that.mType) && TextUtils.equals(this.mSessionId, that.mSessionId);
+        return (this.mType == that.mType) && TextUtils.equals(this.mSessionId, that.mSessionId)
+                && (this.mBypassable == that.mBypassable);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mSessionId);
+        return Objects.hash(mType, mSessionId, mBypassable);
     }
 
     @Override
     public String toString() {
-        return String.format("VpnTransportInfo{type=%d, sessionId=%s}", mType, mSessionId);
+        return String.format("VpnTransportInfo{type=%d, sessionId=%s, bypassable=%b}",
+                mType, mSessionId, mBypassable);
     }
 
     @Override
@@ -106,12 +157,13 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeString(mSessionId);
+        dest.writeBoolean(mBypassable);
     }
 
     public static final @NonNull Creator<VpnTransportInfo> CREATOR =
             new Creator<VpnTransportInfo>() {
         public VpnTransportInfo createFromParcel(Parcel in) {
-            return new VpnTransportInfo(in.readInt(), in.readString());
+            return new VpnTransportInfo(in.readInt(), in.readString(), in.readBoolean());
         }
         public VpnTransportInfo[] newArray(int size) {
             return new VpnTransportInfo[size];