Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3404,12 +3404,25 @@ private static AudioDeviceInfo findAudioDeviceById(int deviceId, int deviceType)
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) return null;
AudioDeviceInfo[] devices = audioManager.getDevices(deviceType);
for (AudioDeviceInfo device : devices) {
if (device.getId() == deviceId) {
return device;
}
}
return null;
int[] ids = new int[devices.length];
String[] names = new String[devices.length];
for (int i = 0; i < devices.length; i++) {
ids[i] = devices[i].getId();
CharSequence name = devices[i].getProductName();
names[i] = name == null ? null : name.toString();
}
// Falls back to matching by the persisted output VID:PID (via its
// current product name) when the exact id isn't found -- a USB
// device's numeric AudioDeviceInfo id is not stable across a
// replug/power-cycle re-enumeration. See
// UsbAudioDevice.resolveDeviceIndex.
UsbDevice fallback = GeneralVariables.usbAudioOutputVendorId == 0 ? null
: UsbAudioDevice.findDeviceByVidPid(context,
GeneralVariables.usbAudioOutputVendorId,
GeneralVariables.usbAudioOutputProductId);
int idx = UsbAudioDevice.resolveDeviceIndex(ids, names, deviceId,
fallback == null ? null : fallback.getProductName());
return idx < 0 ? null : devices[idx];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,63 @@ public UsbAudioDevice.UsbAudioDeviceInfo getUsbAudioDeviceInfo(int position) {
return null;
}

/**
* The {@code AudioManager}-recognized device's own product name at this
* position, when it's a USB-type entry (confirmed on real hardware: the
* QMX shows up this way as "USB-Audio - QMX Transceiver",
* TYPE_USB_DEVICE). Used by the picker to find and persist that device's
* VID:PID via {@link UsbAudioDevice#matchByProductName}, since {@code
* AudioDeviceInfo} itself exposes no VID:PID -- without this, every pick
* of a framework-routed USB device persisted VID 0 (the {@link
* #getUsbAudioDeviceInfo} path only covers USB-direct entries), so the
* VID:PID fallback in {@link #getPositionByDeviceId} (and in {@code
* MicRecorder}/{@code FT8TransmitSignal}) had nothing to fall back to.
*
* @return the product name, or null for the "Default" row, a non-USB
* device, or a USB-direct entry (which already carries its own
* VID:PID via {@link #getUsbAudioDeviceInfo})
*/
public String getUsbFrameworkDeviceProductName(int position) {
if (position <= 0) return null;
int adPos = position - 1;
if (adPos >= audioDeviceList.size()) return null; // USB-direct entry
AudioDeviceInfo device = audioDeviceList.get(adPos);
if (!isUsbType(device.getType())) return null;
CharSequence name = device.getProductName();
return name == null ? null : name.toString();
}

private static boolean isUsbType(int type) {
return type == AudioDeviceInfo.TYPE_USB_DEVICE
|| type == AudioDeviceInfo.TYPE_USB_HEADSET
|| type == AudioDeviceInfo.TYPE_USB_ACCESSORY;
}

/**
* Get the list position for a given device ID.
* deviceId == 0 → 0 (Default)
* deviceId > 0 → search AudioManager devices
* deviceId > 0 → search AudioManager devices by id; if not found (stale
* across a USB replug/power-cycle re-enumeration -- confirmed on real
* hardware: a QMX power-cycle alone changed its id and reverted this
* picker to "Default"), fall back to matching by the persisted
* VID:PID's current product name, which doesn't change across
* reconnects. See {@link UsbAudioDevice#resolveDeviceIndex}.
* deviceId == -1 → search USB audio devices by VID:PID
*/
public int getPositionByDeviceId(int deviceId) {
if (deviceId == 0) return 0;

if (deviceId > 0) {
int[] ids = new int[audioDeviceList.size()];
String[] names = new String[audioDeviceList.size()];
for (int i = 0; i < audioDeviceList.size(); i++) {
if (audioDeviceList.get(i).getId() == deviceId) {
return i + 1;
}
ids[i] = audioDeviceList.get(i).getId();
CharSequence name = audioDeviceList.get(i).getProductName();
names[i] = name == null ? null : name.toString();
}
int idx = UsbAudioDevice.resolveDeviceIndex(ids, names, deviceId,
persistedVidPidProductName());
if (idx >= 0) return idx + 1;
}

// For USB audio (deviceId == -1), match by VID:PID
Expand All @@ -172,6 +214,27 @@ public int getPositionByDeviceId(int deviceId) {
return 0; // fallback to Default
}

/**
* Product name of the already-enumerated {@link #usbAudioDeviceList}
* entry matching this direction's persisted VID:PID, or null if there's
* nothing persisted (VID 0) or no current match.
*/
private String persistedVidPidProductName() {
int vid = isInput
? com.k1af.ft8af.GeneralVariables.usbAudioInputVendorId
: com.k1af.ft8af.GeneralVariables.usbAudioOutputVendorId;
if (vid == 0) return null;
int pid = isInput
? com.k1af.ft8af.GeneralVariables.usbAudioInputProductId
: com.k1af.ft8af.GeneralVariables.usbAudioOutputProductId;
for (UsbAudioDevice.UsbAudioDeviceInfo info : usbAudioDeviceList) {
if (info.device.getVendorId() == vid && info.device.getProductId() == pid) {
return info.device.getProductName();
}
}
return null;
}

@SuppressLint({"ViewHolder", "InflateParams"})
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
Expand Down
52 changes: 43 additions & 9 deletions ft8af/app/src/main/java/com/k1af/ft8af/ui/ConfigFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -906,11 +906,31 @@ public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l)
String.valueOf(GeneralVariables.usbAudioInputProductId));
requestUsbPermissionIfNeeded(usbInfo.device);
} else if (deviceId != -1) {
// Clear USB audio settings if non-USB device selected
GeneralVariables.usbAudioInputVendorId = 0;
GeneralVariables.usbAudioInputProductId = 0;
writeConfig("usbAudioInputVid", "0");
writeConfig("usbAudioInputPid", "0");
// A framework-routed USB device (e.g. "USB-Audio -
// QMX Transceiver") carries no VID:PID of its own --
// find the raw UsbDevice by product-name overlap and
// persist ITS VID:PID, so a later re-enumeration
// (which changes the numeric id but not the product
// name) can still resolve it instead of falling back
// to Default. See AudioDeviceSpinnerAdapter's
// getUsbFrameworkDeviceProductName javadoc.
String productName =
audioInputDeviceAdapter.getUsbFrameworkDeviceProductName(i);
UsbAudioDevice.UsbAudioDeviceInfo rawUsb = productName == null ? null
: UsbAudioDevice.matchByProductName(
UsbAudioDevice.findUsbAudioDevices(requireContext()),
productName, true);
if (rawUsb != null) {
GeneralVariables.usbAudioInputVendorId = rawUsb.device.getVendorId();
GeneralVariables.usbAudioInputProductId = rawUsb.device.getProductId();
} else {
GeneralVariables.usbAudioInputVendorId = 0;
GeneralVariables.usbAudioInputProductId = 0;
}
writeConfig("usbAudioInputVid",
String.valueOf(GeneralVariables.usbAudioInputVendorId));
writeConfig("usbAudioInputPid",
String.valueOf(GeneralVariables.usbAudioInputProductId));
}
}

Expand Down Expand Up @@ -939,10 +959,24 @@ public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l)
String.valueOf(GeneralVariables.usbAudioOutputProductId));
requestUsbPermissionIfNeeded(usbInfo.device);
} else if (deviceId != -1) {
GeneralVariables.usbAudioOutputVendorId = 0;
GeneralVariables.usbAudioOutputProductId = 0;
writeConfig("usbAudioOutputVid", "0");
writeConfig("usbAudioOutputPid", "0");
// Same reasoning as the input picker above.
String productName =
audioOutputDeviceAdapter.getUsbFrameworkDeviceProductName(i);
UsbAudioDevice.UsbAudioDeviceInfo rawUsb = productName == null ? null
: UsbAudioDevice.matchByProductName(
UsbAudioDevice.findUsbAudioDevices(requireContext()),
productName, false);
if (rawUsb != null) {
GeneralVariables.usbAudioOutputVendorId = rawUsb.device.getVendorId();
GeneralVariables.usbAudioOutputProductId = rawUsb.device.getProductId();
} else {
GeneralVariables.usbAudioOutputVendorId = 0;
GeneralVariables.usbAudioOutputProductId = 0;
}
writeConfig("usbAudioOutputVid",
String.valueOf(GeneralVariables.usbAudioOutputVendorId));
writeConfig("usbAudioOutputPid",
String.valueOf(GeneralVariables.usbAudioOutputProductId));
}
}

Expand Down
Loading
Loading