Add AudioDeviceInfoBuilder

This initial version enables the construction of AudioDeviceInfo with the external device types.

PiperOrigin-RevId: 517961411
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java
new file mode 100644
index 0000000..e196187
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java
@@ -0,0 +1,25 @@
+package org.robolectric.shadows;
+
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
+import static android.os.Build.VERSION_CODES.M;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.AudioDeviceInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link AudioDeviceInfoBuilder}. */
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = M)
+public class AudioDeviceInfoBuilderTest {
+
+  @Test
+  public void canCreateAudioDeviceInfoWithDesiredType() {
+    AudioDeviceInfo audioDeviceInfo =
+        AudioDeviceInfoBuilder.newBuilder().setType(TYPE_BLUETOOTH_A2DP).build();
+
+    assertThat(audioDeviceInfo.getType()).isEqualTo(TYPE_BLUETOOTH_A2DP);
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java
new file mode 100644
index 0000000..54cccf0
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java
@@ -0,0 +1,68 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.media.AudioDeviceInfo;
+import android.os.Build.VERSION_CODES;
+import android.util.SparseIntArray;
+import androidx.annotation.RequiresApi;
+import java.util.Optional;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+
+/** Builder for {@link AudioDeviceInfo}. */
+@RequiresApi(VERSION_CODES.M)
+public class AudioDeviceInfoBuilder {
+
+  private int type;
+
+  private AudioDeviceInfoBuilder() {}
+
+  public static AudioDeviceInfoBuilder newBuilder() {
+    return new AudioDeviceInfoBuilder();
+  }
+
+  /**
+   * Sets the device type.
+   *
+   * @param type The device type. The possible values are the constants defined as <a
+   *     href=https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioDeviceInfo.java?q=AudioDeviceType>
+   *     {@code AudioDeviceInfo.AudioDeviceType}</a>
+   */
+  public AudioDeviceInfoBuilder setType(int type) {
+    this.type = type;
+    return this;
+  }
+
+  public AudioDeviceInfo build() {
+    Object port = Shadow.newInstanceOf("android.media.AudioDevicePort");
+    ReflectionHelpers.setField(port, "mType", externalToInternalType(type));
+
+    return ReflectionHelpers.callConstructor(
+        AudioDeviceInfo.class, ClassParameter.from(port.getClass(), port));
+  }
+
+  /** Accessor interface for {@link AudioDeviceInfo}'s internals. */
+  @ForType(AudioDeviceInfo.class)
+  interface AudioDeviceInfoReflector {
+
+    @Static
+    @Accessor("EXT_TO_INT_DEVICE_MAPPING")
+    SparseIntArray getExtToIntDeviceMapping();
+  }
+
+  private static int externalToInternalType(int externalType) {
+    return Optional.ofNullable(
+            reflector(AudioDeviceInfoReflector.class).getExtToIntDeviceMapping().get(externalType))
+        .orElseThrow(
+            () ->
+                new IllegalArgumentException(
+                    "External type "
+                        + externalType
+                        + " does not have a mapping to an internal type defined."));
+  }
+}