Add ShadowVpnManager to Robolectric.

PiperOrigin-RevId: 526856997
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVpnManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVpnManagerTest.java
new file mode 100644
index 0000000..dbf7250
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVpnManagerTest.java
@@ -0,0 +1,95 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Intent;
+import android.net.Ikev2VpnProfile;
+import android.net.VpnManager;
+import android.net.VpnProfileState;
+import android.os.Build.VERSION_CODES;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = VERSION_CODES.R)
+public class ShadowVpnManagerTest {
+  private VpnManager vpnManager;
+  private ShadowVpnManager shadowVpnManager;
+
+  @Before
+  public void setUp() throws Exception {
+    vpnManager = ApplicationProvider.getApplicationContext().getSystemService(VpnManager.class);
+    shadowVpnManager = shadowOf(vpnManager);
+  }
+
+  @Test
+  public void provisionVpnProfile() {
+    Intent intent = new Intent("foo");
+    shadowVpnManager.setProvisionVpnProfileResult(intent);
+
+    assertThat(
+            vpnManager.provisionVpnProfile(
+                new Ikev2VpnProfile.Builder("server", "local.identity")
+                    .setAuthPsk(new byte[0])
+                    .build()))
+        .isSameInstanceAs(intent);
+
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) {
+      VpnProfileState state = vpnManager.getProvisionedVpnProfileState();
+      assertThat(state.getState()).isEqualTo(VpnProfileState.STATE_DISCONNECTED);
+      assertThat(state.getSessionId()).isNull();
+    }
+  }
+
+  @Test
+  public void deleteVpnProfile() {
+    vpnManager.provisionVpnProfile(
+        new Ikev2VpnProfile.Builder("server", "local.identity").setAuthPsk(new byte[0]).build());
+    vpnManager.deleteProvisionedVpnProfile();
+  }
+
+  @Test
+  @Config(minSdk = VERSION_CODES.TIRAMISU)
+  public void deleteVpnProfile_tiramisu() {
+    vpnManager.provisionVpnProfile(
+        new Ikev2VpnProfile.Builder("server", "local.identity").setAuthPsk(new byte[0]).build());
+    assertThat(vpnManager.getProvisionedVpnProfileState()).isNotNull();
+
+    vpnManager.deleteProvisionedVpnProfile();
+    assertThat(vpnManager.getProvisionedVpnProfileState()).isNull();
+  }
+
+  @Test
+  public void startAndStopVpnProfile() {
+    vpnManager.provisionVpnProfile(
+        new Ikev2VpnProfile.Builder("server", "local.identity").setAuthPsk(new byte[0]).build());
+    vpnManager.startProvisionedVpnProfile();
+    vpnManager.stopProvisionedVpnProfile();
+  }
+
+  @Test
+  @Config(minSdk = VERSION_CODES.TIRAMISU)
+  public void startAndStopVpnProfile_tiramisu() {
+    vpnManager.provisionVpnProfile(
+        new Ikev2VpnProfile.Builder("server", "local.identity").setAuthPsk(new byte[0]).build());
+    String sessionKey = vpnManager.startProvisionedVpnProfileSession();
+    VpnProfileState state = vpnManager.getProvisionedVpnProfileState();
+    assertThat(state.getState()).isEqualTo(VpnProfileState.STATE_CONNECTED);
+    assertThat(state.getSessionId()).isEqualTo(sessionKey);
+    assertThat(state.isAlwaysOn()).isFalse();
+    assertThat(state.isLockdownEnabled()).isFalse();
+
+    vpnManager.stopProvisionedVpnProfile();
+    state = vpnManager.getProvisionedVpnProfileState();
+    assertThat(state.getState()).isEqualTo(VpnProfileState.STATE_DISCONNECTED);
+    assertThat(state.getSessionId()).isNull();
+    assertThat(state.isAlwaysOn()).isFalse();
+    assertThat(state.isLockdownEnabled()).isFalse();
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
index 15d5971..17b4ba1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
@@ -59,6 +59,7 @@
 import android.net.INetworkPolicyManager;
 import android.net.INetworkScoreService;
 import android.net.ITetheringConnector;
+import android.net.IVpnManager;
 import android.net.nsd.INsdManager;
 import android.net.vcn.IVcnManagementService;
 import android.net.wifi.IWifiManager;
@@ -209,6 +210,7 @@
       addBinderService(Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class);
       addBinderService(Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class);
       addBinderService(Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class);
+      addBinderService(Context.VPN_MANAGEMENT_SERVICE, IVpnManager.class);
     }
     if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) {
       addBinderService(Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVpnManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVpnManager.java
new file mode 100644
index 0000000..99f807b
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVpnManager.java
@@ -0,0 +1,67 @@
+package org.robolectric.shadows;
+
+import android.content.Intent;
+import android.net.PlatformVpnProfile;
+import android.net.VpnManager;
+import android.net.VpnProfileState;
+import android.os.Build.VERSION_CODES;
+import java.util.UUID;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for {@link VpnManager}. */
+@Implements(value = VpnManager.class, minSdk = VERSION_CODES.R)
+public class ShadowVpnManager {
+
+  private VpnProfileState vpnProfileState;
+  private Intent provisionVpnProfileIntent;
+
+  @Implementation
+  protected void deleteProvisionedVpnProfile() {
+    vpnProfileState = null;
+  }
+
+  @Implementation(minSdk = VERSION_CODES.TIRAMISU)
+  protected VpnProfileState getProvisionedVpnProfileState() {
+    return vpnProfileState;
+  }
+
+  /**
+   * @see #setProvisionVpnProfileResult(Intent).
+   */
+  @Implementation
+  protected Intent provisionVpnProfile(PlatformVpnProfile profile) {
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) {
+      vpnProfileState = new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, null, false, false);
+    }
+    return provisionVpnProfileIntent;
+  }
+
+  /** Sets the return value of #provisionVpnProfile(PlatformVpnProfile). */
+  public void setProvisionVpnProfileResult(Intent intent) {
+    provisionVpnProfileIntent = intent;
+  }
+
+  @Implementation
+  protected void startProvisionedVpnProfile() {
+    startProvisionedVpnProfileSession();
+  }
+
+  @Implementation(minSdk = VERSION_CODES.TIRAMISU)
+  protected String startProvisionedVpnProfileSession() {
+    String sessionKey = UUID.randomUUID().toString();
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) {
+      vpnProfileState =
+          new VpnProfileState(VpnProfileState.STATE_CONNECTED, sessionKey, false, false);
+    }
+    return sessionKey;
+  }
+
+  @Implementation
+  protected void stopProvisionedVpnProfile() {
+    if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.TIRAMISU) {
+      vpnProfileState = new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, null, false, false);
+    }
+  }
+}