diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java index 75531d1727610..a4de8f2994dcc 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Input Terminal interface. * see audio10.pdf section 4.3.2.1 */ -public final class Usb10ACInputTerminal extends UsbACTerminal { +public final class Usb10ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster { private static final String TAG = "Usb10ACInputTerminal"; private byte mNrChannels; // 7:1 1 Channel (0x01) @@ -36,14 +36,17 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { super(length, type, subtype, subclass); } - public byte getNrChannels() { + @Override + public byte getChannelCount() { return mNrChannels; } + @Override public int getChannelConfig() { return mChannelConfig; } + @Override public byte getChannelNames() { return mChannelNames; } @@ -69,9 +72,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { super.report(canvas); canvas.openList(); - canvas.writeListItem("Associated Terminal: " - + ReportCanvas.getHexString(getAssocTerminal())); - canvas.writeListItem("" + getNrChannels() + " Chans. Config: " + canvas.writeListItem("" + getChannelCount() + " Chans. Config: " + ReportCanvas.getHexString(getChannelConfig())); canvas.closeList(); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java index c7634ba7dc633..e89b8f50b6988 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Mixer Interface. * see audio10.pdf section 4.3.2.3 */ -public final class Usb10ACMixerUnit extends UsbACMixerUnit { +public final class Usb10ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster { private static final String TAG = "Usb10ACMixerUnit"; private int mChannelConfig; // Spatial location of output channels @@ -34,11 +34,18 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit { super(length, type, subtype, subClass); } + @Override + public byte getChannelCount() { + return mNumOutputs; + } + + @Override public int getChannelConfig() { return mChannelConfig; } - public byte getChanNameID() { + @Override + public byte getChannelNames() { return mChanNameID; } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java index ee1b32c1b5441..c9c10ce20b134 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Input Terminal interface. * see Audio20.pdf section 3.13.2 Input Terminal */ -public final class Usb20ACInputTerminal extends UsbACTerminal { +public final class Usb20ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster { private static final String TAG = "Usb20ACInputTerminal"; // See Audio20.pdf - Table 4-9 @@ -47,14 +47,21 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { return mClkSourceID; } - public byte getNumChannels() { + @Override + public byte getChannelCount() { return mNumChannels; } - public int getChanConfig() { + @Override + public int getChannelConfig() { return mChanConfig; } + @Override + public byte getChannelNames() { + return mChanNames; + } + public int getControls() { return mControls; } @@ -79,8 +86,8 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { canvas.openList(); canvas.writeListItem("Clock Source: " + getClkSourceID()); - canvas.writeListItem("" + getNumChannels() + " Channels. Config: " - + ReportCanvas.getHexString(getChanConfig())); + canvas.writeListItem("" + getChannelCount() + " Channels. Config: " + + ReportCanvas.getHexString(getChannelConfig())); canvas.closeList(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java index ab965856bb5d2..be932ccd0ffde 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java @@ -20,7 +20,7 @@ package com.android.server.usb.descriptors; * An audio class-specific Mixer Unit interface. * see Audio20.pdf section 4.7.2.6 Mixer Unit Descriptor */ -public final class Usb20ACMixerUnit extends UsbACMixerUnit { +public final class Usb20ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster { private static final String TAG = "Usb20ACMixerUnit"; private int mChanConfig; // 6+p:4 Describes the spatial location of the @@ -37,6 +37,21 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit { super(length, type, subtype, subClass); } + @Override + public byte getChannelCount() { + return mNumOutputs; + } + + @Override + public int getChannelConfig() { + return mChanConfig; + } + + @Override + public byte getChannelNames() { + return mChanNames; + } + @Override public int parseRawDescriptors(ByteStream stream) { super.parseRawDescriptors(stream); diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java index 20a97af49bc5d..9c5b100d16a84 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java @@ -71,6 +71,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal { super.report(canvas); canvas.openList(); + canvas.writeListItem("Source ID:" + getSourceID()); canvas.writeListItem("Clock Source ID: " + getClkSourceID()); canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls())); canvas.writeListItem("Terminal Name ID: " + getTerminalID()); diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java index de2073882e3a2..211ac68214703 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java @@ -21,7 +21,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * Audio20.pdf - 4.9.2 Class-Specific AS Interface Descriptor * 16 bytes */ -public final class Usb20ASGeneral extends UsbACInterface { +public final class Usb20ASGeneral extends UsbACInterface implements UsbAudioChannelCluster { private static final String TAG = "Usb20ASGeneral"; // Audio20.pdf - Table 4-27 @@ -61,14 +61,17 @@ public final class Usb20ASGeneral extends UsbACInterface { return mFormats; } - public byte getNumChannels() { + @Override + public byte getChannelCount() { return mNumChannels; } + @Override public int getChannelConfig() { return mChannelConfig; } + @Override public byte getChannelNames() { return mChannelNames; } @@ -96,7 +99,7 @@ public final class Usb20ASGeneral extends UsbACInterface { canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls())); canvas.writeListItem("Format Type: " + ReportCanvas.getHexString(getFormatType())); canvas.writeListItem("Formats: " + ReportCanvas.getHexString(getFormats())); - canvas.writeListItem("Num Channels: " + getNumChannels()); + canvas.writeListItem("Channel Count: " + getChannelCount()); canvas.writeListItem("Channel Config: " + ReportCanvas.getHexString(getChannelConfig())); canvas.writeListItem("Channel Names String ID: " + getChannelNames()); canvas.closeList(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java index 36139d6c6900e..819e73df955bc 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -66,6 +66,8 @@ public abstract class UsbACTerminal extends UsbACInterface { canvas.writeListItem("Type: " + ReportCanvas.getHexString(terminalType) + ": " + UsbStrings.getTerminalName(terminalType)); canvas.writeListItem("ID: " + ReportCanvas.getHexString(getTerminalID())); + canvas.writeListItem("Associated terminal: " + + ReportCanvas.getHexString(getAssocTerminal())); canvas.closeList(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java new file mode 100644 index 0000000000000..218ba6735cd05 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usb.descriptors; + +/** + * @hide + * Group of logical audio channels that carry tightly related synchronous audio information. + * See Audio10.pdf section 3.7.2.3 Audio Channel Cluster format and Audio20.pdf section 3.13.1 + * audio channel cluster. + */ +public interface UsbAudioChannelCluster { + /** + * @return logical channels in the cluster. + */ + byte getChannelCount(); + + /** + * @return a bit field that indicates which spatial locations are present in the cluster. + */ + int getChannelConfig(); + + /** + * @return index to a string descriptor that describes the spatial location of the first + * non-predefined logical channel in the cluster. + */ + byte getChannelNames(); +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index cd6ea681db07a..f13fcd8d81a48 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -832,31 +832,47 @@ public final class UsbDescriptorParser { return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; } + // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here. + private int getMaximumChannelCount() { + int maxChannelCount = 0; + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbAudioChannelCluster) { + maxChannelCount = Math.max(maxChannelCount, + ((UsbAudioChannelCluster) descriptor).getChannelCount()); + } + } + return maxChannelCount; + } + /** * @hide */ - public float getOutputHeadsetProbability() { + public float getOutputHeadsetLikelihood() { if (hasMIDIInterface()) { return 0.0f; } - float probability = 0.0f; + float likelihood = 0.0f; ArrayList acDescriptors; // Look for a "speaker" boolean hasSpeaker = false; + boolean hasAssociatedInputTerminal = false; + boolean hasHeadphoneOrHeadset = false; acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, UsbACInterface.AUDIO_AUDIOCONTROL); for (UsbDescriptor descriptor : acDescriptors) { if (descriptor instanceof UsbACTerminal) { UsbACTerminal outDescr = (UsbACTerminal) descriptor; - if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER - || outDescr.getTerminalType() - == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES - || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) { hasSpeaker = true; - break; + if (outDescr.getAssocTerminal() != 0x0) { + hasAssociatedInputTerminal = true; + } + } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES + || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + hasHeadphoneOrHeadset = true; } } else { Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() @@ -864,15 +880,27 @@ public final class UsbDescriptorParser { } } - if (hasSpeaker) { - probability += 0.75f; + if (hasHeadphoneOrHeadset) { + likelihood += 0.75f; + } else if (hasSpeaker) { + // The device only reports output terminal as speaker. Try to figure out if the device + // is a headset or not by checking if it has associated input terminal and if multiple + // channels are supported or not. + likelihood += 0.5f; + if (hasAssociatedInputTerminal) { + likelihood += 0.25f; + } + if (getMaximumChannelCount() > 2) { + // When multiple channels are supported, it is less likely to be a headset. + likelihood -= 0.25f; + } } - if (hasSpeaker && hasHIDInterface()) { - probability += 0.25f; + if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) { + likelihood += 0.25f; } - return probability; + return likelihood; } /** @@ -882,7 +910,7 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isOutputHeadset() { - return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; + return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER; } /**