master ohmu/fastfetch-openbsd / src / detection / sound / sound_android.c
ohmu  ·  2026-10-15
  1#include "sound.h"
  2
  3#include "detection/displayserver/displayserver.h"
  4#include "common/android/binder.h"
  5#include "common/android/dex.h"
  6#include "common/debug.h"
  7#include "common/strutil.h"
  8
  9#include <string.h>
 10
 11// Android has no desktop sound stack. PulseAudio is not part of the platform, but a Termux user can
 12// run one, and that is what a session sounds through whenever the display server is a real desktop
 13// rather than SurfaceFlinger -- so this file answers for SurfaceFlinger alone and hands the rest to
 14// sound_linux.c, which speaks PulseAudio. ALSA and OSS are not there either: /dev/snd, /proc/asound
 15// and /sys/class/sound are all EACCES from an app's SELinux domain, `dumpsys audio` needs
 16// android.permission.DUMP, and `settings get system volume_music` needs INTERACT_ACROSS_USERS.
 17// `cmd audio` is worse than useless: `cmd` hands the arguments to the service over binder and the
 18// command body runs inside system_server, so from an app it hangs or answers ENOSYS.
 19//
 20// What is left is the service those all sit on: `audio`, i.e. android.media.IAudioService, which
 21// lives in system_server. This file speaks to it over /dev/binder, the same way the battery, media
 22// and wifi modules do. Two things about it are worth stating up front, because they are what makes
 23// this module work at all:
 24//
 25//   * Nothing here needs a permission. The methods used below are the ones `AudioManager` itself
 26//     calls on behalf of an ordinary app, and the service checks no permission for them. The one
 27//     that does the real work is deliberately named for it -- see getDevicesForAttributesUnprotected
 28//     below. The protected twin, `getDevicesForAttributes`, is refused with
 29//     "Missing MODIFY_AUDIO_ROUTING or QUERY_AUDIO_STATE permissions", and
 30//     `getLastAudibleStreamVolume` with "requires android.permission.QUERY_AUDIO_STATE"; neither is
 31//     used. So unlike the wifi module, this one works on a device where nothing has been granted.
 32//
 33//   * Every transaction code is resolved from the device's own jar at run time, never written down.
 34//     `IAudioService$Stub` declares 283 methods in AOSP and 324 on the test device -- a vendor fork
 35//     inserts methods freely, and an insertion ahead of the one being called shifts it. Reading the
 36//     constants out of the dex is the only thing that survives that; see common/android/dex.h. The
 37//     class is in the *second* dex of framework.jar, which is why ffDexStaticInt walks the entries.
 38//
 39// The reply is not uniform, because the two kinds of service are not: IAudioService is Java, so a
 40// failure arrives as a parcel exception code (-1 for a SecurityException, -4 for a null argument),
 41// while the native services answer with a TF_STATUS_CODE carrying a raw status_t. Only the former
 42// can happen here, but the check is cheap and the two are easy to confuse.
 43
 44#define FF_SOUND_ANDROID_SERVICE "audio"
 45#define FF_SOUND_ANDROID_DESCRIPTOR "android.media.IAudioService"
 46
 47// IAudioService has always been part of framework.jar -- it is not one of the frameworks that moved
 48// into an APEX -- so there is one path and no fallback. The stub is in classes2.dex there.
 49#define FF_SOUND_ANDROID_JAR "/system/framework/framework.jar"
 50#define FF_SOUND_ANDROID_STUB "Landroid/media/IAudioService$Stub;"
 51
 52// The methods this module calls, by the name of the constant the dex carries for each. Spelling the
 53// names out rather than the numbers is the whole point: the lookup turns them into whatever this
 54// build uses, and a build that does not declare one says so instead of reaching another method.
 55#define FF_SOUND_ANDROID_GET_STREAM_VOLUME "TRANSACTION_getStreamVolume"
 56#define FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME "TRANSACTION_getStreamMinVolume"
 57#define FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME "TRANSACTION_getStreamMaxVolume"
 58#define FF_SOUND_ANDROID_IS_STREAM_MUTE "TRANSACTION_isStreamMute"
 59#define FF_SOUND_ANDROID_IS_MUSIC_ACTIVE "TRANSACTION_isMusicActive"
 60#define FF_SOUND_ANDROID_GET_DEVICES_FOR_ATTRIBUTES "TRANSACTION_getDevicesForAttributesUnprotected"
 61#define FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME "TRANSACTION_getBtActiveDeviceName"
 62
 63// `AudioManager.STREAM_MUSIC`. The one stream a phone's volume rocker moves, and the one the module
 64// reports; the other eleven are internal (SYSTEM_ENFORCED, DTMF, ...) or belong to a call.
 65typedef enum FFSoundAndroidStream : int32_t {
 66    FF_SOUND_ANDROID_STREAM_MUSIC = 3,
 67} FFSoundAndroidStream;
 68
 69// `AudioAttributes.USAGE_MEDIA`. Asking the policy what it routes for media is what makes the answer
 70// "where the phone's sound is going"; other usages answer differently on purpose (a call goes to the
 71// earpiece, an emergency call to the telephony port).
 72typedef enum FFSoundAndroidUsage : int32_t {
 73    FF_SOUND_ANDROID_USAGE_MEDIA = 1,
 74} FFSoundAndroidUsage;
 75
 76// `AudioAttributes.SOURCE_*`, and the one value that matters here. A request whose source is
 77// anything but AUDIO_SOURCE_INVALID takes the *capture* branch of
 78// AudioPolicyManager::getDevicesForAttributes and answers with microphones, whatever the usage says:
 79//
 80//     if (attr.source != AUDIO_SOURCE_INVALID) return getInputDevicesForAttributes(attr, devices);
 81//
 82// AUDIO_SOURCE_DEFAULT is 0, so leaving the field at zero -- which is what a zeroed parcel does --
 83// silently reports the built-in microphone as the output device. It has to be written out as -1.
 84#define FF_SOUND_ANDROID_SOURCE_INVALID (-1)
 85
 86// `AudioAttributes.ATTR_PARCEL_IS_NULL_BUNDLE`. The tags and the bundle are the only variable-length
 87// parts of the request; an empty tag list and no bundle is the smallest well-formed one.
 88#define FF_SOUND_ANDROID_ATTR_NULL_BUNDLE (-1977)
 89
 90// The interface token is 12 bytes of header plus a padded string16, and the largest request here adds
 91// one AudioAttributes to it. The replies are one int, or one list of AudioDeviceAttributes. The
 92// device reply is sized well past the 48 bytes a single device takes, because a reply that does not
 93// fit is reported as a buffer error and would hide the exception message the service sent instead.
 94#define FF_SOUND_ANDROID_SCALAR_PARCEL_SIZE 96
 95#define FF_SOUND_ANDROID_SCALAR_REPLY_SIZE 32
 96#define FF_SOUND_ANDROID_DEVICE_PARCEL_SIZE 128
 97#define FF_SOUND_ANDROID_DEVICE_REPLY_SIZE 1024
 98
 99// The reply to the Bluetooth device name request is one string16, and a device name is chosen by
100// whoever owns the headset: 248 bytes is the longest a Bluetooth name can be, so 512 bytes of reply
101// hold the count, the code units, the terminator and the padding with room to spare. A reply that
102// does not fit is reported as a buffer error, so sizing it generously is what keeps a long name
103// from looking like a failure.
104#define FF_SOUND_ANDROID_NAME_REPLY_SIZE 512
105
106// `AudioDeviceInfo.TYPE_*`, as the test device's own dex declares them (read out of
107// `Landroid/media/AudioDeviceInfo;` -- most recently on Android 17, which had added four). The
108// value is what `AudioDeviceAttributes.type` carries in the reply; the names are the AOSP constant
109// names without the prefix, which is what goes into `identifier`, and a spelled out name, which is
110// what the module prints. An unrecognised value is reported rather than guessed at: a vendor is
111// free to add types, and a wrong name is worse than "Unknown Device".
112typedef enum FFSoundAndroidDeviceType : int32_t {
113    FF_SOUND_ANDROID_TYPE_UNKNOWN = 0,
114    FF_SOUND_ANDROID_TYPE_BUILTIN_EARPIECE = 1,
115    FF_SOUND_ANDROID_TYPE_BUILTIN_SPEAKER = 2,
116    FF_SOUND_ANDROID_TYPE_WIRED_HEADSET = 3,
117    FF_SOUND_ANDROID_TYPE_WIRED_HEADPHONES = 4,
118    FF_SOUND_ANDROID_TYPE_LINE_ANALOG = 5,
119    FF_SOUND_ANDROID_TYPE_LINE_DIGITAL = 6,
120    FF_SOUND_ANDROID_TYPE_BLUETOOTH_SCO = 7,
121    FF_SOUND_ANDROID_TYPE_BLUETOOTH_A2DP = 8,
122    FF_SOUND_ANDROID_TYPE_HDMI = 9,
123    FF_SOUND_ANDROID_TYPE_HDMI_ARC = 10,
124    FF_SOUND_ANDROID_TYPE_USB_DEVICE = 11,
125    FF_SOUND_ANDROID_TYPE_USB_ACCESSORY = 12,
126    FF_SOUND_ANDROID_TYPE_DOCK = 13,
127    FF_SOUND_ANDROID_TYPE_FM = 14,
128    FF_SOUND_ANDROID_TYPE_BUILTIN_MIC = 15,
129    FF_SOUND_ANDROID_TYPE_FM_TUNER = 16,
130    FF_SOUND_ANDROID_TYPE_TV_TUNER = 17,
131    FF_SOUND_ANDROID_TYPE_TELEPHONY = 18,
132    FF_SOUND_ANDROID_TYPE_AUX_LINE = 19,
133    FF_SOUND_ANDROID_TYPE_IP = 20,
134    FF_SOUND_ANDROID_TYPE_BUS = 21,
135    FF_SOUND_ANDROID_TYPE_USB_HEADSET = 22,
136    FF_SOUND_ANDROID_TYPE_HEARING_AID = 23,
137    FF_SOUND_ANDROID_TYPE_BUILTIN_SPEAKER_SAFE = 24,
138    FF_SOUND_ANDROID_TYPE_REMOTE_SUBMIX = 25,
139    FF_SOUND_ANDROID_TYPE_BLE_HEADSET = 26,
140    FF_SOUND_ANDROID_TYPE_BLE_SPEAKER = 27,
141    FF_SOUND_ANDROID_TYPE_ECHO_REFERENCE = 28,
142    FF_SOUND_ANDROID_TYPE_HDMI_EARC = 29,
143    FF_SOUND_ANDROID_TYPE_BLE_BROADCAST = 30,
144    FF_SOUND_ANDROID_TYPE_DOCK_ANALOG = 31,
145    FF_SOUND_ANDROID_TYPE_MULTICHANNEL_GROUP = 32,
146    FF_SOUND_ANDROID_TYPE_BLE_HEARING_AID = 33,
147    FF_SOUND_ANDROID_TYPE_BLE_CENTRAL = 34,
148    FF_SOUND_ANDROID_TYPE_BLE_CENTRAL_BROADCAST = 35,
149    // 36 to 79 have no constant in the Android 17 release, and 80 is the highest it declares.
150    FF_SOUND_ANDROID_TYPE_PROXY = 80,
151} FFSoundAndroidDeviceType;
152
153typedef struct FFSoundAndroidDeviceName {
154    FFSoundAndroidDeviceType type;
155    const char* identifier;
156    const char* name;
157} FFSoundAndroidDeviceName;
158
159static const FFSoundAndroidDeviceName FF_SOUND_ANDROID_DEVICE_NAMES[] = {
160    { FF_SOUND_ANDROID_TYPE_UNKNOWN, "UNKNOWN", "Unknown Device" },
161    { FF_SOUND_ANDROID_TYPE_BUILTIN_EARPIECE, "BUILTIN_EARPIECE", "Built-in Earpiece" },
162    { FF_SOUND_ANDROID_TYPE_BUILTIN_SPEAKER, "BUILTIN_SPEAKER", "Built-in Speaker" },
163    { FF_SOUND_ANDROID_TYPE_WIRED_HEADSET, "WIRED_HEADSET", "Wired Headset" },
164    { FF_SOUND_ANDROID_TYPE_WIRED_HEADPHONES, "WIRED_HEADPHONES", "Wired Headphones" },
165    { FF_SOUND_ANDROID_TYPE_LINE_ANALOG, "LINE_ANALOG", "Line (Analog)" },
166    { FF_SOUND_ANDROID_TYPE_LINE_DIGITAL, "LINE_DIGITAL", "Line (Digital)" },
167    { FF_SOUND_ANDROID_TYPE_BLUETOOTH_SCO, "BLUETOOTH_SCO", "Bluetooth SCO" },
168    { FF_SOUND_ANDROID_TYPE_BLUETOOTH_A2DP, "BLUETOOTH_A2DP", "Bluetooth A2DP" },
169    { FF_SOUND_ANDROID_TYPE_HDMI, "HDMI", "HDMI" },
170    { FF_SOUND_ANDROID_TYPE_HDMI_ARC, "HDMI_ARC", "HDMI ARC" },
171    { FF_SOUND_ANDROID_TYPE_USB_DEVICE, "USB_DEVICE", "USB Device" },
172    { FF_SOUND_ANDROID_TYPE_USB_ACCESSORY, "USB_ACCESSORY", "USB Accessory" },
173    { FF_SOUND_ANDROID_TYPE_DOCK, "DOCK", "Dock" },
174    { FF_SOUND_ANDROID_TYPE_FM, "FM", "FM" },
175    { FF_SOUND_ANDROID_TYPE_BUILTIN_MIC, "BUILTIN_MIC", "Built-in Microphone" },
176    { FF_SOUND_ANDROID_TYPE_FM_TUNER, "FM_TUNER", "FM Tuner" },
177    { FF_SOUND_ANDROID_TYPE_TV_TUNER, "TV_TUNER", "TV Tuner" },
178    { FF_SOUND_ANDROID_TYPE_TELEPHONY, "TELEPHONY", "Telephony" },
179    { FF_SOUND_ANDROID_TYPE_AUX_LINE, "AUX_LINE", "Aux Line" },
180    { FF_SOUND_ANDROID_TYPE_IP, "IP", "IP" },
181    { FF_SOUND_ANDROID_TYPE_BUS, "BUS", "Bus" },
182    { FF_SOUND_ANDROID_TYPE_USB_HEADSET, "USB_HEADSET", "USB Headset" },
183    { FF_SOUND_ANDROID_TYPE_HEARING_AID, "HEARING_AID", "Hearing Aid" },
184    { FF_SOUND_ANDROID_TYPE_BUILTIN_SPEAKER_SAFE, "BUILTIN_SPEAKER_SAFE", "Built-in Speaker (Safe)" },
185    { FF_SOUND_ANDROID_TYPE_REMOTE_SUBMIX, "REMOTE_SUBMIX", "Remote Submix" },
186    { FF_SOUND_ANDROID_TYPE_BLE_HEADSET, "BLE_HEADSET", "Bluetooth LE Headset" },
187    { FF_SOUND_ANDROID_TYPE_BLE_SPEAKER, "BLE_SPEAKER", "Bluetooth LE Speaker" },
188    { FF_SOUND_ANDROID_TYPE_ECHO_REFERENCE, "ECHO_REFERENCE", "Echo Reference" },
189    { FF_SOUND_ANDROID_TYPE_HDMI_EARC, "HDMI_EARC", "HDMI eARC" },
190    { FF_SOUND_ANDROID_TYPE_BLE_BROADCAST, "BLE_BROADCAST", "Bluetooth LE Broadcast" },
191    { FF_SOUND_ANDROID_TYPE_DOCK_ANALOG, "DOCK_ANALOG", "Dock (Analog)" },
192    { FF_SOUND_ANDROID_TYPE_MULTICHANNEL_GROUP, "MULTICHANNEL_GROUP", "Multichannel Group" },
193    { FF_SOUND_ANDROID_TYPE_BLE_HEARING_AID, "BLE_HEARING_AID", "Bluetooth LE Hearing Aid" },
194    { FF_SOUND_ANDROID_TYPE_BLE_CENTRAL, "BLE_CENTRAL", "Bluetooth LE Central" },
195    { FF_SOUND_ANDROID_TYPE_BLE_CENTRAL_BROADCAST, "BLE_CENTRAL_BROADCAST", "Bluetooth LE Central Broadcast" },
196    { FF_SOUND_ANDROID_TYPE_PROXY, "PROXY", "Proxy" },
197};
198
199static const FFSoundAndroidDeviceName* soundAndroidDeviceName(int32_t type) {
200    for (size_t i = 0; i < ARRAY_SIZE(FF_SOUND_ANDROID_DEVICE_NAMES); ++i) {
201        if ((int32_t) FF_SOUND_ANDROID_DEVICE_NAMES[i].type == type) {
202            return &FF_SOUND_ANDROID_DEVICE_NAMES[i];
203        }
204    }
205    return nullptr;
206}
207
208// The types that stand for a Bluetooth device. They are the ones whose own name is worth asking the
209// service for: everything else is described well enough by its type, and the request is one more
210// round trip to system_server. A hearing aid is on the list because it is a Bluetooth LE link with
211// a name of its own, the same as a headset. The other three types Android 17 added stay off it:
212// BLE_CENTRAL and BLE_CENTRAL_BROADCAST name an audio path inside this device while it operates in
213// a Bluetooth peripheral mode rather than a device on the far end of a link, and PROXY is a logical
214// endpoint rather than a link at all.
215static bool soundAndroidIsBluetooth(int32_t type) {
216    switch ((FFSoundAndroidDeviceType) type) {
217        case FF_SOUND_ANDROID_TYPE_BLUETOOTH_SCO:
218        case FF_SOUND_ANDROID_TYPE_BLUETOOTH_A2DP:
219        case FF_SOUND_ANDROID_TYPE_HEARING_AID:
220        case FF_SOUND_ANDROID_TYPE_BLE_HEARING_AID:
221        case FF_SOUND_ANDROID_TYPE_BLE_HEADSET:
222        case FF_SOUND_ANDROID_TYPE_BLE_SPEAKER:
223        case FF_SOUND_ANDROID_TYPE_BLE_BROADCAST:
224            return true;
225        default:
226            return false;
227    }
228}
229
230// Every transaction code this module sends, resolved from the device's own jar in one walk. They are
231// asked for together because the walk is dominated by the dex's type table and all seven sit in the
232// same class of the same entry -- see common/android/dex.h.
233typedef struct FFSoundAndroidCodes {
234    int32_t getStreamVolume;
235    int32_t getStreamMinVolume;
236    int32_t getStreamMaxVolume;
237    int32_t isStreamMute;
238    int32_t getDevicesForAttributes;
239    int32_t isMusicActive;
240    int32_t getBtActiveDeviceName;
241} FFSoundAndroidCodes;
242
243// The five codes the module cannot answer without, against the two that only refine the answer. A
244// build that does not declare one of these five has nothing to report, which is what the module did
245// when each code was resolved on its own.
246static bool soundAndroidCodesIncomplete(const FFSoundAndroidCodes* codes) {
247    return codes->getStreamVolume == FF_DEX_STATIC_INT_UNRESOLVED
248        || codes->getStreamMinVolume == FF_DEX_STATIC_INT_UNRESOLVED
249        || codes->getStreamMaxVolume == FF_DEX_STATIC_INT_UNRESOLVED
250        || codes->isStreamMute == FF_DEX_STATIC_INT_UNRESOLVED
251        || codes->getDevicesForAttributes == FF_DEX_STATIC_INT_UNRESOLVED;
252}
253
254// Resolves all seven in one call, and then makes the judgement the reader leaves to its caller. The
255// two that only refine the answer are allowed to come back as the sentinel, because a build may
256// declare one of them and not the other -- Mi 10 has `getDevicesForAttributesUnprotected` and no
257// `getBtActiveDeviceName` -- and a code the module can live without must not take it down with it.
258static const char* soundAndroidResolveCodes(FFSoundAndroidCodes* codes) {
259    const FFDexStaticIntRequest requests[] = {
260        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_VOLUME, &codes->getStreamVolume },
261        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME, &codes->getStreamMinVolume },
262        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME, &codes->getStreamMaxVolume },
263        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_IS_STREAM_MUTE, &codes->isStreamMute },
264        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_DEVICES_FOR_ATTRIBUTES, &codes->getDevicesForAttributes },
265        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_IS_MUSIC_ACTIVE, &codes->isMusicActive },
266        { FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, &codes->getBtActiveDeviceName },
267    };
268
269    const char* error = ffDexStaticInts(FF_SOUND_ANDROID_JAR, requests, ARRAY_SIZE(requests));
270    if (error != nullptr) {
271        FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_SOUND_ANDROID_JAR, error);
272        return error;
273    }
274    if (soundAndroidCodesIncomplete(codes)) {
275        FF_DEBUG("The audio service does not declare every method this module calls");
276        return "The audio service does not declare every method this module calls";
277    }
278    return nullptr;
279}
280
281// Calls a method that takes no argument or one int, and answers with one int. `transaction` comes out
282// of the jar, and the sentinel the reader leaves for a code it could not find is caught here, so that
283// a build which does not declare the method is a message rather than a transaction that means
284// something else. `transactionField` is carried only to name the method in the debug output, which a
285// release build compiles out -- hence the attribute.
286static const char* soundAndroidCallInt(FFBinder* binder, uint32_t handle, [[maybe_unused]] const char* transactionField, int32_t transaction, bool hasArgument, int32_t argument, int32_t* result) {
287    if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
288        FF_DEBUG("The audio service does not declare that method");
289        return "The audio service does not declare that method";
290    }
291
292    uint8_t parcelBuffer[FF_SOUND_ANDROID_SCALAR_PARCEL_SIZE];
293    FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
294    ffBinderParcelPutInterfaceToken(&parcel, FF_SOUND_ANDROID_DESCRIPTOR);
295    if (hasArgument) {
296        ffBinderParcelPutI32(&parcel, argument);
297    }
298
299    uint8_t replyBuffer[FF_SOUND_ANDROID_SCALAR_REPLY_SIZE];
300    FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
301    const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
302    if (error != nullptr) {
303        return error;
304    }
305    if (ffBinderReplyIsStatus(&reply)) {
306        FF_DEBUG("\"%s\" resolved to transaction %d, which this build does not answer", transactionField, transaction);
307        return "The audio service does not answer that request";
308    }
309    const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
310    if (exception != 0) {
311        FF_DEBUG("\"%s\" is transaction %d and raised exception %d", transactionField, transaction, exception);
312        return "The audio service raised an exception";
313    }
314    *result = ffBinderReadI32(reply.data, reply.size, 4);
315    FF_DEBUG("\"%s\" is transaction %d and answered %d", transactionField, transaction, *result);
316    return nullptr;
317}
318
319// Walks past a string16 and reports how long it was. The bytes themselves are not decoded: the only
320// strings in the reply are the device address and name, both of which are empty for every built-in
321// device and a partly anonymised MAC for a Bluetooth one -- `AudioService` blanks the address of
322// anything it considers identifying before answering an unprivileged caller, so neither is an
323// identifier worth carrying.
324static bool soundAndroidSkipString16(const uint8_t* data, size_t size, size_t* offset) {
325    const int32_t length = ffBinderReadI32(data, size, *offset);
326    *offset += sizeof(int32_t);
327    if (length < 0) {
328        return true;
329    }
330    // The code units plus the NUL terminator, padded to four bytes.
331    const size_t payload = ((size_t) length * 2 + 2 + 3) & ~(size_t) 3;
332    if (*offset + payload > size) {
333        return false;
334    }
335    *offset += payload;
336    return true;
337}
338
339// Reads a string16 out of a reply and appends it to `result`.
340//
341// The code units are UTF-16LE, and a device name is the first string here that is not guaranteed to
342// be ASCII -- a headset can be named in any script, and `vivo TWS A4` is only the one that happened
343// to be paired -- so they are converted rather than copied. A count of -1 is how the same field
344// spells null, which is what the service answers when there is no name to give; that is not a
345// failure, and the caller falls back to the type name.
346static bool soundAndroidReadString16(const uint8_t* data, size_t size, size_t offset, FFstrbuf* result) {
347    if (offset + sizeof(int32_t) > size) {
348        return false;
349    }
350    const int32_t length = ffBinderReadI32(data, size, offset);
351    offset += sizeof(int32_t);
352    if (length < 0) {
353        return true;
354    }
355    // The code units plus the NUL terminator. The padding behind them is not part of the string, so
356    // there is nothing to step over here.
357    if ((size_t) length * 2 + 2 > size - offset) {
358        return false;
359    }
360
361    for (int32_t i = 0; i < length; ++i) {
362        uint32_t codePoint = (uint32_t) data[offset + (size_t) i * 2] | ((uint32_t) data[offset + (size_t) i * 2 + 1] << 8);
363        if (codePoint >= 0xD800 && codePoint <= 0xDBFF && i + 1 < length) {
364            // Anything outside the basic plane arrives as a surrogate pair, and the low half is what
365            // turns it back into one code point. A high surrogate without one is malformed and is
366            // passed through as it stands rather than guessed at.
367            const uint32_t low = (uint32_t) data[offset + (size_t) (i + 1) * 2] | ((uint32_t) data[offset + (size_t) (i + 1) * 2 + 1] << 8);
368            if (low >= 0xDC00 && low <= 0xDFFF) {
369                codePoint = 0x10000u + ((codePoint - 0xD800u) << 10) + (low - 0xDC00u);
370                ++i;
371            }
372        }
373        ffStrbufAppendUtf32CodePoint(result, codePoint);
374    }
375    return true;
376}
377
378// Calls `getBtActiveDeviceName()`, which takes no argument and answers a string16. It is the only
379// call here that carries a human readable device name.
380//
381// The policy reply has a `name` field of its own, but on the test device `AudioService` leaves it
382// empty for every device -- a Bluetooth one included, whose address it blanks to
383// `XX:XX:XX:XX:D8:61` while the name goes with it -- so the type is all that reply can describe a
384// sink by. This call is what turns "Bluetooth A2DP" into the name of the headset.
385//
386// What it must not be used for is deciding *which* device is the output. It answers about the
387// Bluetooth route as a whole and reports it even when the policy has selected the speaker, so it is
388// only ever consulted after the device list has already picked a Bluetooth sink, and only its name
389// is taken from it.
390static const char* soundAndroidCallBtName(FFBinder* binder, uint32_t handle, int32_t transaction, FFstrbuf* result) {
391    if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
392        FF_DEBUG("The audio service does not declare getBtActiveDeviceName");
393        return "The audio service does not declare getBtActiveDeviceName";
394    }
395
396    uint8_t parcelBuffer[FF_SOUND_ANDROID_SCALAR_PARCEL_SIZE];
397    FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
398    ffBinderParcelPutInterfaceToken(&parcel, FF_SOUND_ANDROID_DESCRIPTOR);
399
400    uint8_t replyBuffer[FF_SOUND_ANDROID_NAME_REPLY_SIZE];
401    FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
402    const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
403    if (error != nullptr) {
404        return error;
405    }
406    if (ffBinderReplyIsStatus(&reply)) {
407        FF_DEBUG("\"%s\" resolved to transaction %d, which this build does not answer", FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, transaction);
408        return "The audio service does not answer that request";
409    }
410    const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
411    if (exception != 0) {
412        FF_DEBUG("\"%s\" is transaction %d and raised exception %d", FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, transaction, exception);
413        return "The audio service raised an exception";
414    }
415    if (!soundAndroidReadString16(reply.data, reply.size, sizeof(int32_t), result)) {
416        FF_DEBUG("The audio service answered a truncated device name");
417        return "The audio service answered a truncated device name";
418    }
419    FF_DEBUG("\"%s\" is transaction %d and answered \"%s\"", FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, transaction,
420        result->chars != nullptr ? result->chars : "");
421    return nullptr;
422}
423
424// Reads one `AudioDeviceAttributes` out of the reply and advances `offset` past it.
425//
426// The layout is the one `AudioDeviceAttributes.writeToParcel` writes, and it was checked byte by
427// byte against the device's replies:
428//
429//     int32 role, int32 type, string16 address, string16 name, int32 nativeType,
430//     List<AudioProfile> profiles, List<AudioProfile> descriptors
431//
432// `role` is 1 for a source (an input) and 2 for a sink (an output). `type` is the
433// AudioDeviceInfo.TYPE_* above; `nativeType` repeats it as the older AUDIO_DEVICE_* bit value and is
434// not used. Neither profile list has carried an entry on any device seen so far, so they are walked
435// only far enough to confirm that. A device that does carry profiles is still reported -- everything
436// this module reads comes before them -- but nothing after it in the list can be reached, because
437// walking a profile means decoding one, and the caller stops there rather than reading the next
438// entry out of the middle of one.
439static bool soundAndroidReadDevice(const uint8_t* data, size_t size, size_t* offset, int32_t* type, int32_t* role, bool* ended) {
440    *ended = false;
441    if (*offset + 3 * sizeof(int32_t) > size) {
442        return false;
443    }
444    *role = ffBinderReadI32(data, size, *offset);
445    *offset += sizeof(int32_t);
446    *type = ffBinderReadI32(data, size, *offset);
447    *offset += sizeof(int32_t);
448    if (!soundAndroidSkipString16(data, size, offset) || !soundAndroidSkipString16(data, size, offset)) {
449        return false;
450    }
451    *offset += sizeof(int32_t); // nativeType, which follows the two strings
452    for (int list = 0; list < 2; ++list) {
453        const int32_t count = ffBinderReadI32(data, size, *offset);
454        *offset += sizeof(int32_t);
455        if (count > 0) {
456            // A null list is a -1 and occupies nothing beyond the count that was just read, so only
457            // a list with entries in it ends the walk.
458            *ended = true;
459            return true;
460        }
461    }
462    return true;
463}
464
465// Turns the three volume readings into the percentage the module prints. The service answers with
466// the index it keeps for the stream, which is not a percentage and is not even on a fixed scale:
467// `getStreamVolume` divides by ten before returning, so the numbers here are the ones an app sees,
468// and the maximum differs per stream -- 150 for music on the test device, 15 for the system stream,
469// 10 for a call. Music's minimum is 0, but that is a property of the stream rather than of the API:
470// a call's minimum is 1. So the percentage comes out of the range the service reports, and a stream
471// whose range is empty or inverted is reported as unknown rather than as 100%.
472static uint8_t soundAndroidPercent(int32_t volume, int32_t minimum, int32_t maximum, int32_t mute) {
473    if (mute != 0) {
474        // A muted stream is reported as 0 rather than as its index: the service keeps the index and
475        // the mute flag separately, and it is the mute flag that decides what comes out of the
476        // speaker. This is also what the PulseAudio backend does with a muted sink.
477        return 0;
478    }
479    if (maximum <= minimum) {
480        return FF_SOUND_VOLUME_UNKNOWN;
481    }
482    if (volume <= minimum) {
483        return 0;
484    }
485    if (volume >= maximum) {
486        return 100;
487    }
488    return (uint8_t) (((volume - minimum) * 100 + (maximum - minimum) / 2) / (maximum - minimum));
489}
490
491static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
492    // Resolved before anything else so that a build without the methods says so, rather than opening
493    // binder and failing later with a message about the transport. All seven codes are asked for in
494    // one call and judged here: the five the module cannot answer without decide whether it runs at
495    // all, and the two it only uses when the build declares them are left as sentinels for their
496    // call sites to cope with.
497    FFSoundAndroidCodes codes = {};
498    const char* error = soundAndroidResolveCodes(&codes);
499    if (error != nullptr) {
500        return error;
501    }
502
503    [[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
504    error = ffBinderOpen(&binder);
505    if (error != nullptr) {
506        return error;
507    }
508
509    // Released on every path out, including the early returns below.
510    [[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
511    error = ffBinderLookupService(&binder, FF_SOUND_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
512    if (error != nullptr) {
513        return error;
514    }
515    FF_DEBUG("The \"%s\" service is handle %u, getDevicesForAttributesUnprotected is transaction %d",
516        FF_SOUND_ANDROID_SERVICE, service.handle, codes.getDevicesForAttributes);
517
518    uint8_t parcelBuffer[FF_SOUND_ANDROID_DEVICE_PARCEL_SIZE];
519    FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
520    ffBinderParcelPutInterfaceToken(&parcel, FF_SOUND_ANDROID_DESCRIPTOR);
521    // The non-null marker `Parcel.writeTypedObject` puts in front of a Parcelable. The service reads
522    // the argument with `readTypedObject`, which reads that int first and hands back a null object
523    // when it is not 1 -- so leaving it out does not shift the fields, it loses the argument, and
524    // the service answers with an exception rather than a device list.
525    ffBinderParcelPutI32(&parcel, 1);
526    // `in AudioAttributes`, whose writeToParcel is: usage, contentType, source, flags, the parcel
527    // flags, the tag array and the bundle marker. A null bundle is the only form built here.
528    ffBinderParcelPutI32(&parcel, (int32_t) FF_SOUND_ANDROID_USAGE_MEDIA);
529    ffBinderParcelPutI32(&parcel, 0); // contentType, unused by the policy when a usage is set
530    ffBinderParcelPutI32(&parcel, FF_SOUND_ANDROID_SOURCE_INVALID);
531    ffBinderParcelPutI32(&parcel, 0); // flags
532    ffBinderParcelPutI32(&parcel, 0); // parcel flags: the tags are not flattened
533    ffBinderParcelPutI32(&parcel, 0); // an empty tag array
534    ffBinderParcelPutI32(&parcel, FF_SOUND_ANDROID_ATTR_NULL_BUNDLE);
535
536    uint8_t replyBuffer[FF_SOUND_ANDROID_DEVICE_REPLY_SIZE];
537    FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
538    error = ffBinderTransact(&binder, service.handle, (uint32_t) codes.getDevicesForAttributes, 0, &parcel, &reply);
539    if (error != nullptr) {
540        return error;
541    }
542    if (ffBinderReplyIsStatus(&reply)) {
543        FF_DEBUG("The reply carries status %d instead of a parcel", (int) ffBinderReadI32(reply.data, reply.size, 0));
544        return "The audio service rejected the request";
545    }
546
547    const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
548    if (exception != 0) {
549        FF_DEBUG("getDevicesForAttributesUnprotected raised exception %d", exception);
550        return "The audio service raised an exception";
551    }
552
553    // The service answers with the devices the policy has selected for that usage, so there can be
554    // more than one -- an alarm or a ringtone plays on the speaker and on a connected headset at the
555    // same time -- and the first is the one the stream's volume belongs to.
556    const int32_t count = ffBinderReadI32(reply.data, reply.size, 4);
557    size_t offset = 2 * sizeof(int32_t);
558    const FFSoundAndroidDeviceName* name = nullptr;
559    int32_t deviceType = 0;
560    for (int32_t index = 0; index < count; ++index) {
561        const int32_t present = ffBinderReadI32(reply.data, reply.size, offset);
562        offset += sizeof(int32_t);
563        if (present != 1) {
564            continue;
565        }
566
567        int32_t type = 0;
568        int32_t role = 0;
569        bool ended = false;
570        if (!soundAndroidReadDevice(reply.data, reply.size, &offset, &type, &role, &ended)) {
571            FF_DEBUG("The device list ended after %d of %d entries", index, count);
572            break;
573        }
574
575        if (role == 2 && name == nullptr) {
576            // ROLE_SINK. A source would be a microphone, which the policy only answers when the
577            // request asks for one, so this is a guard rather than a case that happens.
578            name = soundAndroidDeviceName(type);
579            deviceType = type;
580            FF_DEBUG("Media output device %d of %d is type %d, which is \"%s\"",
581                index + 1, count, type, name != nullptr ? name->identifier : "an unrecognised type");
582        }
583
584        if (ended) {
585            // A profile list with entries in it: the next entry cannot be reached without walking
586            // one, and the first device is the one that matters.
587            FF_DEBUG("Device %d carries audio profiles, so the rest of the list is not read", index);
588            break;
589        }
590    }
591
592    if (name == nullptr) {
593        FF_DEBUG("The audio service selected no output device");
594        return "The audio service selected no output device";
595    }
596
597    int32_t volume = 0;
598    int32_t minimum = 0;
599    int32_t maximum = 0;
600    int32_t mute = 0;
601    error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_VOLUME, codes.getStreamVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &volume);
602    if (error == nullptr) {
603        error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME, codes.getStreamMinVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &minimum);
604    }
605    if (error == nullptr) {
606        error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME, codes.getStreamMaxVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &maximum);
607    }
608    if (error == nullptr) {
609        error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_STREAM_MUTE, codes.isStreamMute, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &mute);
610    }
611    if (error != nullptr) {
612        return error;
613    }
614
615    uint8_t percent = soundAndroidPercent(volume, minimum, maximum, mute);
616    FF_DEBUG("Stream %d is %d of %d..%d, muted %d, which is %s",
617        (int) FF_SOUND_ANDROID_STREAM_MUSIC, volume, minimum, maximum, mute,
618        percent != FF_SOUND_VOLUME_UNKNOWN ? "a percentage" : "unknown");
619
620    // `isMusicActive` is what tells "this device is the output" apart from "this device is playing".
621    // It answers about the media stream as a whole and not about one device, so it only ever adds
622    // the ACTIVE bit to the device the policy already selected. A build that does not declare the
623    // method, or a service that refuses it, leaves the bit clear rather than failing the module.
624    int32_t active = 0;
625    const char* activeError = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_MUSIC_ACTIVE, codes.isMusicActive, true, 0, &active);
626    if (activeError != nullptr) {
627        FF_DEBUG("Whether anything is playing is not known: %s", activeError);
628        active = 0;
629    }
630
631    const FFSoundType type = (FFSoundType) (FF_SOUND_TYPE_MAIN | (active != 0 ? FF_SOUND_TYPE_ACTIVE : FF_SOUND_TYPE_NONE));
632    if ((options->soundType & FF_SOUND_TYPE_MAIN) && !(type & FF_SOUND_TYPE_MAIN)) {
633        return nullptr;
634    }
635    if ((options->soundType & FF_SOUND_TYPE_ACTIVE) && !(type & FF_SOUND_TYPE_ACTIVE)) {
636        return nullptr;
637    }
638
639    // A Bluetooth sink is reported by the name of the device behind it when the service can be
640    // asked, and by its type otherwise. Falling back is not an error path: a build that does not
641    // declare the method, a service that refuses it, and a service that has no name to give all end
642    // in the type name, which is what this module printed before it asked at all.
643    FF_STRBUF_AUTO_DESTROY bluetoothName = ffStrbufCreate();
644    if (soundAndroidIsBluetooth(deviceType)) {
645        const char* nameError = soundAndroidCallBtName(&binder, service.handle, codes.getBtActiveDeviceName, &bluetoothName);
646        if (nameError != nullptr || bluetoothName.length == 0) {
647            FF_DEBUG("The Bluetooth device is not named: %s",
648                nameError != nullptr ? nameError : "the service answered an empty name");
649        }
650    }
651
652    FFSoundDevice* device = FF_LIST_ADD(FFSoundDevice, *devices);
653    // The identifier stays the type: it is the machine readable half of the pair, and the Bluetooth
654    // address the reply carries is not usable as one -- `AudioService` blanks it to
655    // `XX:XX:XX:XX:D8:61` for an unprivileged caller.
656    ffStrbufInitS(&device->identifier, name->identifier);
657    if (bluetoothName.length > 0) {
658        ffStrbufInitMove(&device->name, &bluetoothName);
659    } else {
660        ffStrbufInitS(&device->name, name->name);
661    }
662    ffStrbufInitStatic(&device->platformApi, "AudioService");
663    device->volume = percent;
664    device->type = type;
665    FF_DEBUG("Reporting \"%s\" (%s) at %u%%%s", device->name.chars, device->identifier.chars, device->volume,
666        active != 0 ? ", active" : "");
667    return nullptr;
668}
669
670const char* ffDetectSound(FFSoundOptions* options, FFlist* devices) {
671    // The binder route below asks the policy about Android's own audio, which is the audio that is
672    // audible only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs
673    // a real desktop on top of the device, and the sound there belongs to that desktop, not to
674    // Android -- so that case is handed to the Linux implementation, which speaks PulseAudio. Sound
675    // is only reported for one of the two, never merged.
676    const FFDisplayServerResult* wm = ffConnectDisplayServer();
677    if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
678        FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars);
679        const char* ffDetectSoundLinux(FFSoundOptions* options, FFlist* devices);
680        return ffDetectSoundLinux(options, devices);
681    }
682
683    return detectNative(options, devices);
684}