From 89d0c849907504112f3114ed749176c4bb6f7b2e Mon Sep 17 00:00:00 2001 From: jiabin Date: Thu, 19 May 2022 23:47:13 +0000 Subject: [PATCH] Update logic for determining if an USB device is a headset or not. Update the function of calculating likelihood of being an output headset. 1. Change the function name from probability to likelihood. It is fine to have the value be negative or greater than 1.0f. 2. If there is an output terminal with type as HEADPHONES or BIDIR_HEADSET, likelihood += 0.75, goto 5. Otherwise, goto 2. 3. If there is an output terminal with type as SPEAKER, likelihood += 0.5. Goto 3. 4. If there is associated input terminal for an output terminal exists, likelihood += 0.25. Goto 4. 5. If multiple channels are supported, likelihood -= 0.25. Goto 5. 6. If there is an output terminal with type as SPEAKER, HEADPHONES or BIDIR_HEADSET and there is HID interface, likelihood += 0.25. Test: connect USB devices and check the reported device type. Bug: 231973571 Change-Id: I98e433b5333f8bb317b6ae89bd65fee2c42236eb --- .../usb/descriptors/Usb10ACInputTerminal.java | 11 ++-- .../usb/descriptors/Usb10ACMixerUnit.java | 11 +++- .../usb/descriptors/Usb20ACInputTerminal.java | 17 ++++-- .../usb/descriptors/Usb20ACMixerUnit.java | 17 +++++- .../descriptors/Usb20ACOutputTerminal.java | 1 + .../usb/descriptors/Usb20ASGeneral.java | 9 ++-- .../server/usb/descriptors/UsbACTerminal.java | 2 + .../descriptors/UsbAudioChannelCluster.java | 41 ++++++++++++++ .../usb/descriptors/UsbDescriptorParser.java | 54 ++++++++++++++----- 9 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java 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; } /**