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
This commit is contained in:
jiabin
2022-05-19 23:47:13 +00:00
parent f749b88590
commit 89d0c84990
9 changed files with 134 additions and 29 deletions

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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());

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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<UsbDescriptor> 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;
}
/**