diff --git a/core/api/current.txt b/core/api/current.txt index 385899a078a5..92b0822a16ff 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20787,10 +20787,13 @@ package android.media { method public android.media.AudioAttributes.Builder setUsage(int); } - public class AudioDescriptor { + public class AudioDescriptor implements android.os.Parcelable { + method public int describeContents(); method @NonNull public byte[] getDescriptor(); method public int getEncapsulationType(); method public int getStandard(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int STANDARD_EDID = 1; // 0x1 field public static final int STANDARD_NONE = 0; // 0x0 } @@ -21304,14 +21307,17 @@ package android.media { method @NonNull public android.media.AudioPresentation.Builder setProgramId(int); } - public class AudioProfile { + public class AudioProfile implements android.os.Parcelable { + method public int describeContents(); method @NonNull public int[] getChannelIndexMasks(); method @NonNull public int[] getChannelMasks(); method public int getEncapsulationType(); method public int getFormat(); method @NonNull public int[] getSampleRates(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; // 0x1 field public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator CREATOR; } public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 17ea0858b7c0..9fc0b651aa99 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5671,11 +5671,20 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int); } + public class AudioDescriptor implements android.os.Parcelable { + ctor public AudioDescriptor(int, int, @NonNull byte[]); + } + public final class AudioDeviceAttributes implements android.os.Parcelable { ctor public AudioDeviceAttributes(@NonNull android.media.AudioDeviceInfo); ctor public AudioDeviceAttributes(int, int, @NonNull String); + ctor public AudioDeviceAttributes(int, int, @NonNull String, @NonNull String, @NonNull java.util.List, @NonNull java.util.List); method public int describeContents(); + method public boolean equalTypeAddress(@Nullable Object); method @NonNull public String getAddress(); + method @NonNull public java.util.List getAudioDescriptors(); + method @NonNull public java.util.List getAudioProfiles(); + method @NonNull public String getName(); method public int getRole(); method public int getType(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -5826,6 +5835,10 @@ package android.media { field public static final int PLAYER_TYPE_UNKNOWN = -1; // 0xffffffff } + public class AudioProfile implements android.os.Parcelable { + ctor public AudioProfile(int, @NonNull int[], @NonNull int[], @NonNull int[], int); + } + public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException; method public static long getMaxSharedAudioHistoryMillis(); diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java index 11371b11e905..df648be4c157 100644 --- a/media/java/android/media/AudioDescriptor.java +++ b/media/java/android/media/AudioDescriptor.java @@ -18,16 +18,21 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; /** * The AudioDescriptor contains the information to describe the audio playback/capture * capabilities. The capabilities are described by a byte array, which is defined by a * particular standard. This is used when the format is unrecognized to the platform. */ -public class AudioDescriptor { +public class AudioDescriptor implements Parcelable { /** * The audio standard is not specified. */ @@ -49,7 +54,15 @@ public class AudioDescriptor { private final byte[] mDescriptor; private final int mEncapsulationType; - AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) { + /** + * @hide + * Constructor from standard, encapsulation type and descriptor + * @param standard the standard of the audio descriptor + * @param encapsulationType the encapsulation type of the audio descriptor + * @param descriptor the audio descriptor + */ + @SystemApi + public AudioDescriptor(int standard, int encapsulationType, @NonNull byte[] descriptor) { mStandard = standard; mEncapsulationType = encapsulationType; mDescriptor = descriptor; @@ -87,4 +100,66 @@ public class AudioDescriptor { public @AudioProfile.EncapsulationType int getEncapsulationType() { return mEncapsulationType; } + + @Override + public int hashCode() { + return Objects.hash(mStandard, mEncapsulationType, Arrays.hashCode(mDescriptor)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioDescriptor that = (AudioDescriptor) o; + return ((mStandard == that.mStandard) + && (mEncapsulationType == that.mEncapsulationType) + && (Arrays.equals(mDescriptor, that.mDescriptor))); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + sb.append("standard=" + mStandard); + sb.append(", encapsulation type=" + mEncapsulationType); + if (mDescriptor != null && mDescriptor.length > 0) { + sb.append(", descriptor=").append(Arrays.toString(mDescriptor)); + } + sb.append("}"); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStandard); + dest.writeInt(mEncapsulationType); + dest.writeByteArray(mDescriptor); + } + + private AudioDescriptor(@NonNull Parcel in) { + mStandard = in.readInt(); + mEncapsulationType = in.readInt(); + mDescriptor = in.createByteArray(); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + /** + * Rebuilds an AudioDescriptor previously stored with writeToParcel(). + * @param p Parcel object to read the AudioDescriptor from + * @return a new AudioDescriptor created from the data in the parcel + */ + public AudioDescriptor createFromParcel(Parcel p) { + return new AudioDescriptor(p); + } + + public AudioDescriptor[] newArray(int size) { + return new AudioDescriptor[size]; + } + }; } diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 1448c49105b2..4ce0440944a8 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -18,12 +18,16 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -65,16 +69,27 @@ public final class AudioDeviceAttributes implements Parcelable { * The unique address of the device. Some devices don't have addresses, only an empty string. */ private final @NonNull String mAddress; - + /** + * The non-unique name of the device. Some devices don't have names, only an empty string. + * Should not be used as a unique identifier for a device. + */ + private final @NonNull String mName; /** * Is input or output device */ private final @Role int mRole; - /** * The internal audio device type */ private final int mNativeType; + /** + * List of AudioProfiles supported by the device + */ + private final @NonNull List mAudioProfiles; + /** + * List of AudioDescriptors supported by the device + */ + private final @NonNull List mAudioDescriptors; /** * @hide @@ -88,7 +103,10 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT; mType = deviceInfo.getType(); mAddress = deviceInfo.getAddress(); + mName = String.valueOf(deviceInfo.getProductName()); mNativeType = deviceInfo.getInternalType(); + mAudioProfiles = deviceInfo.getAudioProfiles(); + mAudioDescriptors = deviceInfo.getAudioDescriptors(); } /** @@ -100,7 +118,24 @@ public final class AudioDeviceAttributes implements Parcelable { */ @SystemApi public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, - @NonNull String address) { + @NonNull String address) { + this(role, type, address, "", new ArrayList<>(), new ArrayList<>()); + } + + /** + * @hide + * Constructor with specification of all attributes + * @param role indicates input or output role + * @param type the device type, as defined in {@link AudioDeviceInfo} + * @param address the address of the device, or an empty string for devices without one + * @param name the name of the device, or an empty string for devices without one + * @param profiles the list of AudioProfiles supported by the device + * @param descriptors the list of AudioDescriptors supported by the device + */ + @SystemApi + public AudioDeviceAttributes(@Role int role, @AudioDeviceInfo.AudioDeviceType int type, + @NonNull String address, @NonNull String name, @NonNull List profiles, + @NonNull List descriptors) { Objects.requireNonNull(address); if (role != ROLE_OUTPUT && role != ROLE_INPUT) { throw new IllegalArgumentException("Invalid role " + role); @@ -118,19 +153,26 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = role; mType = type; mAddress = address; + mName = name; + mAudioProfiles = profiles; + mAudioDescriptors = descriptors; } /** * @hide - * Constructor from internal device type and address - * @param type the internal device type, as defined in {@link AudioSystem} + * Constructor called from AudioSystem JNI when creating an AudioDeviceAttributes from a native + * AudioDeviceTypeAddr instance. + * @param nativeType the internal device type, as defined in {@link AudioSystem} * @param address the address of the device, or an empty string for devices without one */ public AudioDeviceAttributes(int nativeType, @NonNull String address) { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; + mName = ""; mNativeType = nativeType; + mAudioProfiles = new ArrayList<>(); + mAudioDescriptors = new ArrayList<>(); } /** @@ -163,6 +205,16 @@ public final class AudioDeviceAttributes implements Parcelable { return mAddress; } + /** + * @hide + * Returns the name of the audio device, or an empty string for devices without one + * @return the device name + */ + @SystemApi + public @NonNull String getName() { + return mName; + } + /** * @hide * Returns the internal device type of a device @@ -172,9 +224,29 @@ public final class AudioDeviceAttributes implements Parcelable { return mNativeType; } + /** + * @hide + * Returns the list of AudioProfiles supported by the device + * @return the list of AudioProfiles + */ + @SystemApi + public @NonNull List getAudioProfiles() { + return mAudioProfiles; + } + + /** + * @hide + * Returns the list of AudioDescriptors supported by the device + * @return the list of AudioDescriptors + */ + @SystemApi + public @NonNull List getAudioDescriptors() { + return mAudioDescriptors; + } + @Override public int hashCode() { - return Objects.hash(mRole, mType, mAddress); + return Objects.hash(mRole, mType, mAddress, mName, mAudioProfiles, mAudioDescriptors); } @Override @@ -182,6 +254,25 @@ public final class AudioDeviceAttributes implements Parcelable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; + AudioDeviceAttributes that = (AudioDeviceAttributes) o; + return ((mRole == that.mRole) + && (mType == that.mType) + && mAddress.equals(that.mAddress) + && mName.equals(that.mName) + && mAudioProfiles.equals(that.mAudioProfiles) + && mAudioDescriptors.equals(that.mAudioDescriptors)); + } + + /** + * Returns true if the role, type and address are equal. Called to compare with an + * AudioDeviceAttributes that was created from a native AudioDeviceTypeAddr instance. + * @param o object to compare with + * @return whether role, type and address are equal + */ + public boolean equalTypeAddress(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AudioDeviceAttributes that = (AudioDeviceAttributes) o; return ((mRole == that.mRole) && (mType == that.mType) @@ -199,7 +290,10 @@ public final class AudioDeviceAttributes implements Parcelable { + " role:" + roleToString(mRole) + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType) : AudioSystem.getInputDeviceName(mNativeType)) - + " addr:" + mAddress); + + " addr:" + mAddress + + " name:" + mName + + " profiles:" + mAudioProfiles.toString() + + " descriptors:" + mAudioDescriptors.toString()); } @Override @@ -212,14 +306,26 @@ public final class AudioDeviceAttributes implements Parcelable { dest.writeInt(mRole); dest.writeInt(mType); dest.writeString(mAddress); + dest.writeString(mName); dest.writeInt(mNativeType); + dest.writeParcelableArray( + mAudioProfiles.toArray(new AudioProfile[mAudioProfiles.size()]), flags); + dest.writeParcelableArray( + mAudioDescriptors.toArray(new AudioDescriptor[mAudioDescriptors.size()]), flags); } private AudioDeviceAttributes(@NonNull Parcel in) { mRole = in.readInt(); mType = in.readInt(); mAddress = in.readString(); + mName = in.readString(); mNativeType = in.readInt(); + AudioProfile[] audioProfilesArray = + in.readParcelableArray(AudioProfile.class.getClassLoader(), AudioProfile.class); + mAudioProfiles = new ArrayList(Arrays.asList(audioProfilesArray)); + AudioDescriptor[] audioDescriptorsArray = in.readParcelableArray( + AudioDescriptor.class.getClassLoader(), AudioDescriptor.class); + mAudioDescriptors = new ArrayList(Arrays.asList(audioDescriptorsArray)); } public static final @NonNull Parcelable.Creator CREATOR = diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 211a50ee93af..59e109f1d36b 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -413,7 +413,7 @@ public final class AudioDeviceInfo { */ public CharSequence getProductName() { String portName = mPort.name(); - return portName.length() != 0 ? portName : android.os.Build.MODEL; + return (portName != null && portName.length() != 0) ? portName : android.os.Build.MODEL; } /** diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java index ae8d0a5ad4ab..5c5f837dd07a 100644 --- a/media/java/android/media/AudioProfile.java +++ b/media/java/android/media/AudioProfile.java @@ -18,10 +18,14 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -33,7 +37,7 @@ import java.util.stream.Collectors; * be reported in different audio profiles. The application can choose any of the encapsulation * types. */ -public class AudioProfile { +public class AudioProfile implements Parcelable { /** * No encapsulation type is specified. */ @@ -57,9 +61,19 @@ public class AudioProfile { private final int[] mChannelIndexMasks; private final int mEncapsulationType; - AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks, - @NonNull int[] channelIndexMasks, - int encapsulationType) { + /** + * @hide + * Constructor from format, sampling rates, channel masks, channel index masks and + * encapsulation type. + * @param format the audio format + * @param samplingRates the supported sampling rates + * @param channelMasks the supported channel masks + * @param channelIndexMasks the supported channel index masks + * @param encapsulationType the encapsulation type of the encoding format + */ + @SystemApi + public AudioProfile(int format, @NonNull int[] samplingRates, @NonNull int[] channelMasks, + @NonNull int[] channelIndexMasks, int encapsulationType) { mFormat = format; mSamplingRates = samplingRates; mChannelMasks = channelMasks; @@ -113,6 +127,26 @@ public class AudioProfile { return mEncapsulationType; } + @Override + public int hashCode() { + return Objects.hash(mFormat, Arrays.hashCode(mSamplingRates), + Arrays.hashCode(mChannelMasks), Arrays.hashCode(mChannelIndexMasks), + mEncapsulationType); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioProfile that = (AudioProfile) o; + return ((mFormat == that.mFormat) + && (hasIdenticalElements(mSamplingRates, that.mSamplingRates)) + && (hasIdenticalElements(mChannelMasks, that.mChannelMasks)) + && (hasIdenticalElements(mChannelIndexMasks, that.mChannelIndexMasks)) + && (mEncapsulationType == that.mEncapsulationType)); + } + @Override public String toString() { StringBuilder sb = new StringBuilder("{"); @@ -126,6 +160,7 @@ public class AudioProfile { if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) { sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks)); } + sb.append(", encapsulation type=" + mEncapsulationType); sb.append("}"); return sb.toString(); } @@ -137,4 +172,50 @@ public class AudioProfile { return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt)) .collect(Collectors.joining(", ")); } + + private static boolean hasIdenticalElements(int[] array1, int[] array2) { + int[] sortedArray1 = Arrays.copyOf(array1, array1.length); + Arrays.sort(sortedArray1); + int[] sortedArray2 = Arrays.copyOf(array2, array2.length); + Arrays.sort(sortedArray2); + return Arrays.equals(sortedArray1, sortedArray2); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mFormat); + dest.writeIntArray(mSamplingRates); + dest.writeIntArray(mChannelMasks); + dest.writeIntArray(mChannelIndexMasks); + dest.writeInt(mEncapsulationType); + } + + private AudioProfile(@NonNull Parcel in) { + mFormat = in.readInt(); + mSamplingRates = in.createIntArray(); + mChannelMasks = in.createIntArray(); + mChannelIndexMasks = in.createIntArray(); + mEncapsulationType = in.readInt(); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + /** + * Rebuilds an AudioProfile previously stored with writeToParcel(). + * @param p Parcel object to read the AudioProfile from + * @return a new AudioProfile created from the data in the parcel + */ + public AudioProfile createFromParcel(Parcel p) { + return new AudioProfile(p); + } + + public AudioProfile[] newArray(int size) { + return new AudioProfile[size]; + } + }; }