master ohmu/fastfetch-openbsd / src / detection / media / media_android.c
ohmu  ·  2026-10-15
   1#include "media.h"
   2#include "common/android/api.h"
   3#include "common/android/applabel.h"
   4#include "common/android/binder.h"
   5#include "common/android/dex.h"
   6#include "common/android/package.h"
   7#include "common/debug.h"
   8#include "common/io.h"
   9#include "common/mallocHelper.h"
  10#include "detection/displayserver/displayserver.h"
  11
  12#include <android/bitmap.h>
  13#include <android/data_space.h>
  14
  15#include <errno.h>
  16#include <fcntl.h>
  17#include <stdint.h>
  18#include <stdio.h>
  19#include <sys/mman.h>
  20#include <time.h>
  21#include <unistd.h>
  22
  23// Android's "now playing" belongs to the media_session system service. There is no C API for it --
  24// the NDK exposes neither the service nor a Java bridge -- and `/system/bin/dumpsys media_session`
  25// costs a fork/exec (measured at ~70 ms on this device) while answering with less. This file speaks
  26// to the service over /dev/binder through common/android/binder.h instead, the same way the wallpaper
  27// and wifi modules do, and pays ~0.1 ms per call.
  28//
  29// The gate is a single method. `ISessionManager.getSessions(ComponentName, int)` is the only entry
  30// point that checks a permission: MediaSessionService.enforceMediaPermissions() admits the system UI,
  31// a holder of android.permission.MEDIA_CONTENT_CONTROL and an enabled notification listener, and
  32// refuses everything else -- measured on an Android 16 device, an app UID opens binder, finds the
  33// service and reaches onTransact, and is then answered with a SecurityException carrying the server's
  34// own stack (`enforceMediaPermissions` <- `verifySessionsRequest` <- `getSessions`), while the shell
  35// UID gets the session list from the same transaction code. Nothing in this file tests the uid: the
  36// service is asked and its answer is believed, so a device whose policy is looser works without a
  37// change here. What the answer does depend on is the ComponentName in the request, which is not a
  38// filter and not decoration either -- see the request build in androidDetectMediaSession().
  39//
  40// Only one of the two routes is ever asked, and which one it is depends on the display server. The
  41// binder route is Android's, and the session it reads is the one the phone is playing only while
  42// SurfaceFlinger is the display server; a Termux:X11 or Wayland session runs a real compositor on top
  43// of the device, and the players running there are not Android media sessions at all -- so that case
  44// is handed to the MPRIS implementation in media_linux.c, which is compiled alongside this file for
  45// exactly this reason.
  46//
  47// Under SurfaceFlinger the Android session is still not the whole story. When it has nothing to say --
  48// an app UID, or a phone with nothing registered -- the module falls back to that same MPRIS
  49// implementation, over the session bus. That bus is open to every process in the session, so the
  50// fallback is the only route an app UID has and a real one for the shell UID too.
  51//
  52// What the service answers with is worth the trouble, and more than the dump carries. The reply to
  53// getSessions is a list of session tokens, and each token *is* an ISessionController binder -- there
  54// is no getController() hop -- and every argument-less getter on that interface was measured to
  55// answer without a permission check, so the one gate above is the whole story. getMetadata yields the
  56// title, the artist, the album and, unlike the dump, the track length; getPlaybackState yields the
  57// state, the position and the playback speed; and the artwork comes across as a Bitmap whose pixels
  58// arrive as an ashmem descriptor, which is what makes a real cover possible here.
  59//
  60// Two traps, both measured:
  61//
  62//   * A request must ask with TF_ACCEPT_FDS, or a reply that carries a descriptor is refused with
  63//     BR_FAILED_REPLY -- which reads exactly like a service that is not running. The failure is
  64//     asymmetric and that is what makes it expensive: only metadata carrying artwork has a
  65//     descriptor, so a probe tried against one player passes while the same probe fails against the
  66//     next one.
  67//   * A Bundle value is written by Parcel.writeValue, and for a Parcelable that means an int32 byte
  68//     length in front of the class name (Parcel.isLengthPrefixed). Reading the class name without
  69//     accounting for it, or reading a Bitmap as if it were a string, desynchronises every field after
  70//     it. The length is also the one thing that makes an unknown Parcelable skippable.
  71//
  72#define FF_MEDIA_ANDROID_SERVICE "media_session"
  73
  74#define FF_MEDIA_ANDROID_SESSIONS_DESCRIPTOR "android.media.session.ISessionManager"
  75#define FF_MEDIA_ANDROID_CONTROLLER_DESCRIPTOR "android.media.session.ISessionController"
  76
  77// The jar and the stub classes the transaction codes are read out of. Both interfaces have always
  78// been part of framework.jar -- neither is one of the frameworks that moved into an APEX -- so there
  79// is one path and no fallback.
  80#define FF_MEDIA_ANDROID_JAR "/system/framework/framework.jar"
  81#define FF_MEDIA_ANDROID_SESSIONS_STUB "Landroid/media/session/ISessionManager$Stub;"
  82#define FF_MEDIA_ANDROID_CONTROLLER_STUB "Landroid/media/session/ISessionController$Stub;"
  83
  84// The methods this module calls, by the name of the constant the dex carries for each. Spelling the
  85// names out rather than the numbers is the whole point: the numbering is a property of the `.aidl`
  86// the ROM was built from, and a vendor that inserts a method ahead of one shifts it, after which a
  87// transcribed code reaches a different method or none. `getSessions` is 2 from Android 12 on but 3 on
  88// Android 11, where `notifySession2Created` holds the second slot -- which is exactly the drift a
  89// hardcoded number cannot survive. See common/android/dex.h.
  90#define FF_MEDIA_ANDROID_GET_SESSIONS "TRANSACTION_getSessions"
  91#define FF_MEDIA_ANDROID_GET_PACKAGE_NAME "TRANSACTION_getPackageName"
  92#define FF_MEDIA_ANDROID_GET_METADATA "TRANSACTION_getMetadata"
  93#define FF_MEDIA_ANDROID_GET_PLAYBACK_STATE "TRANSACTION_getPlaybackState"
  94
  95// The largest reply any of these getters produced is a metadata Bundle at ~1.6 KB, and the artwork
  96// never lands here -- it arrives as a descriptor. 16 KB leaves room for a Bundle of unusually long
  97// strings; a reply that did not fit would be reported rather than misread.
  98#define FF_MEDIA_ANDROID_REPLY_SIZE (16 * 1024)
  99
 100// ffAndroidGetOwnPackage() writes at most this many bytes, terminator included.
 101#define FF_MEDIA_ANDROID_PACKAGE_SIZE 128
 102
 103// The request is the interface token, the ComponentName and the user id. A ComponentName is a
 104// presence marker in front of the package name and the class name, so the two string16s are what
 105// took this past the 128 it used to be. That size had room for a seven-character package name --
 106// `com.termux` with an empty class is already 136 bytes -- so the size is derived from the longest
 107// name that can be written rather than rounded up by hand, because a request that does not fit is
 108// dropped and reported as truncated: that fails the whole detection instead of degrading to a call
 109// without a name. The empty class name costs a 4-byte length, a NUL terminator and the padding, so 8.
 110// clang-format off
 111#define FF_MEDIA_ANDROID_REQUEST_SIZE \
 112    (12 + ((4 + 2 * sizeof(FF_MEDIA_ANDROID_SESSIONS_DESCRIPTOR) + 3) & ~3u) \
 113        + 4 + ((4 + 2 * FF_MEDIA_ANDROID_PACKAGE_SIZE + 3) & ~3u) + 8u + 4u)
 114// clang-format on
 115
 116// Lossless WebP reads quality as effort rather than as fidelity: 0 is "compress quickly and accept a
 117// larger file", 100 is "spend the time". Measured on a 363x363 cover, the whole range is worth 1.2%
 118// of the file for 2.4 times the time, so there is nothing at the top to buy.
 119#define FF_MEDIA_ANDROID_COVER_QUALITY 0
 120
 121// One reply buffer for every call in this file: the calls are sequential, and the metadata of the
 122// winning session is the last one to need it.
 123static uint8_t s_reply[FF_MEDIA_ANDROID_REPLY_SIZE];
 124
 125// ---------------------------------------------------------------------------------------------
 126// Reading a Parcel
 127// ---------------------------------------------------------------------------------------------
 128
 129// android.os.Parcel value types, from Parcel.VAL_*. A MediaMetadata Bundle only ever holds the four
 130// below; anything else stops the walk there rather than being guessed at, and the caller is told.
 131typedef enum FFMediaAndroidValueType : int32_t {
 132    FF_MEDIA_ANDROID_VAL_NULL = -1,
 133    FF_MEDIA_ANDROID_VAL_STRING = 0,
 134    FF_MEDIA_ANDROID_VAL_INTEGER = 1,
 135    FF_MEDIA_ANDROID_VAL_PARCELABLE = 4,
 136    FF_MEDIA_ANDROID_VAL_LONG = 6,
 137    FF_MEDIA_ANDROID_VAL_CHAR_SEQUENCE = 10,
 138} FFMediaAndroidValueType;
 139
 140// Bundle.writeToParcel writes this in front of the entry count. It is the byte pattern 'BNDL', and
 141// checking it is what tells a bundle apart from the length word of something else.
 142#define FF_MEDIA_ANDROID_BUNDLE_MAGIC 0x4C444E42u
 143
 144#define FF_MEDIA_ANDROID_CLASS_BITMAP "android.graphics.Bitmap"
 145
 146#define FF_MEDIA_ANDROID_KEY_PREFIX "android.media.metadata."
 147#define FF_MEDIA_ANDROID_KEY_TITLE FF_MEDIA_ANDROID_KEY_PREFIX "TITLE"
 148#define FF_MEDIA_ANDROID_KEY_DISPLAY_TITLE FF_MEDIA_ANDROID_KEY_PREFIX "DISPLAY_TITLE"
 149#define FF_MEDIA_ANDROID_KEY_ARTIST FF_MEDIA_ANDROID_KEY_PREFIX "ARTIST"
 150#define FF_MEDIA_ANDROID_KEY_DISPLAY_SUBTITLE FF_MEDIA_ANDROID_KEY_PREFIX "DISPLAY_SUBTITLE"
 151#define FF_MEDIA_ANDROID_KEY_ALBUM FF_MEDIA_ANDROID_KEY_PREFIX "ALBUM"
 152#define FF_MEDIA_ANDROID_KEY_DURATION FF_MEDIA_ANDROID_KEY_PREFIX "DURATION"
 153#define FF_MEDIA_ANDROID_KEY_MEDIA_URI FF_MEDIA_ANDROID_KEY_PREFIX "MEDIA_URI"
 154#define FF_MEDIA_ANDROID_KEY_ART FF_MEDIA_ANDROID_KEY_PREFIX "ART"
 155#define FF_MEDIA_ANDROID_KEY_ALBUM_ART FF_MEDIA_ANDROID_KEY_PREFIX "ALBUM_ART"
 156#define FF_MEDIA_ANDROID_KEY_DISPLAY_ICON FF_MEDIA_ANDROID_KEY_PREFIX "DISPLAY_ICON"
 157
 158// MediaMetadata carries the artwork under three keys, and Android documents them as: ART is "the
 159// artwork for the media", ALBUM_ART "the artwork for the album it is from", DISPLAY_ICON "a small
 160// icon representing the media". They are usually the same image; ART is the one the system UI puts on
 161// the lock screen, so it wins when more than one is present.
 162typedef enum FFMediaAndroidCoverPriority : uint32_t {
 163    FF_MEDIA_ANDROID_COVER_DISPLAY_ICON = 1,
 164    FF_MEDIA_ANDROID_COVER_ALBUM_ART = 2,
 165    FF_MEDIA_ANDROID_COVER_ART = 3,
 166} FFMediaAndroidCoverPriority;
 167
 168typedef struct FFMediaAndroidReader {
 169    const uint8_t* data;
 170    size_t size;
 171    size_t position;
 172} FFMediaAndroidReader;
 173
 174[[gnu::nonnull(1, 2)]] static bool androidReadI32(FFMediaAndroidReader* reader, int32_t* value) {
 175    if (reader->position + sizeof(int32_t) > reader->size) {
 176        return false;
 177    }
 178    memcpy(value, reader->data + reader->position, sizeof(int32_t));
 179    reader->position += sizeof(int32_t);
 180    return true;
 181}
 182
 183[[gnu::nonnull(1, 2)]] static bool androidReadI64(FFMediaAndroidReader* reader, int64_t* value) {
 184    if (reader->position + sizeof(int64_t) > reader->size) {
 185        return false;
 186    }
 187    memcpy(value, reader->data + reader->position, sizeof(int64_t));
 188    reader->position += sizeof(int64_t);
 189    return true;
 190}
 191
 192// AIDL string16: an int32 code-unit count, that many UTF-16LE code units, a UTF-16 NUL terminator,
 193// and padding to four bytes. The terminator is walked over even though it is not part of the string,
 194// and so is the padding -- getting either wrong shifts every following field. A count of -1 is how
 195// the same field spells null.
 196[[gnu::nonnull(1, 2)]] static bool androidReadString16(FFMediaAndroidReader* reader, FFstrbuf* result) {
 197    int32_t length = 0;
 198    if (!androidReadI32(reader, &length)) {
 199        return false;
 200    }
 201    if (length < 0) {
 202        ffStrbufClear(result);
 203        return true;
 204    }
 205
 206    const size_t units = (size_t) length * 2;
 207    const size_t padded = (units + 2 + 3) & ~(size_t) 3; // the code units, the two byte NUL, then padding
 208    if (reader->position + padded > reader->size) {
 209        return false;
 210    }
 211
 212    const uint8_t* codeUnits = reader->data + reader->position;
 213    reader->position += padded;
 214
 215    ffStrbufClear(result);
 216    for (size_t i = 0; i < (size_t) length; i++) {
 217        uint32_t codepoint = (uint32_t) codeUnits[i * 2] | ((uint32_t) codeUnits[i * 2 + 1] << 8);
 218        if (codepoint >= 0xD800 && codepoint <= 0xDBFF && i + 1 < (size_t) length) {
 219            const uint32_t low = (uint32_t) codeUnits[(i + 1) * 2] | ((uint32_t) codeUnits[(i + 1) * 2 + 1] << 8);
 220            if (low >= 0xDC00 && low <= 0xDFFF) {
 221                codepoint = 0x10000u + ((codepoint - 0xD800u) << 10) + (low - 0xDC00u);
 222                ++i;
 223            }
 224        }
 225        ffStrbufAppendUtf32CodePoint(result, codepoint);
 226    }
 227    return true;
 228}
 229
 230// ---------------------------------------------------------------------------------------------
 231// The artwork
 232// ---------------------------------------------------------------------------------------------
 233
 234typedef struct FFMediaAndroidCover {
 235    int fd; // a descriptor the reply handed over; whoever read the reply closes it
 236    uint32_t width;
 237    uint32_t height;
 238    uint32_t priority;
 239    bool found;
 240} FFMediaAndroidCover;
 241
 242// The pixels of a Bitmap cross the transaction as an ashmem descriptor, and the descriptor's length is
 243// the one thing that ties the blob back to its Bitmap: Parcel::writeBlob writes the byte count in
 244// front of the descriptor, so that count and the descriptor's own size must agree -- and the same
 245// identity (`width * height * 4`) is what makes the two dimensions recoverable without knowing the
 246// rest of Bitmap's parcel layout, which is written by native code and has changed between releases.
 247//
 248// Nothing is assumed about where in the body the dimensions sit: the walk is over the whole body and
 249// the arithmetic is the filter, so a wrong candidate is rejected rather than believed. A Bitmap whose
 250// pixels were small enough to be written inline has no descriptor at all and answers nothing here.
 251[[gnu::nonnull(1, 3, 4)]] static bool androidTakeBitmap(const uint8_t* body, size_t length, const FFBinderReply* reply, FFMediaAndroidCover* cover) {
 252    for (uint32_t i = 0; i < reply->fdCount; i++) {
 253        const off_t size = lseek(reply->fds[i], 0, SEEK_END);
 254        if (size <= 0 || size > (off_t) UINT32_MAX || (size % 4) != 0) {
 255            continue;
 256        }
 257
 258        size_t blobOffset = SIZE_MAX;
 259        for (size_t offset = 0; offset + sizeof(int32_t) <= length; offset += sizeof(int32_t)) {
 260            int32_t word = 0;
 261            memcpy(&word, body + offset, sizeof(word));
 262            if (word > 0 && (uint32_t) word == (uint32_t) size) {
 263                blobOffset = offset;
 264                break;
 265            }
 266        }
 267        if (blobOffset == SIZE_MAX) {
 268            continue;
 269        }
 270
 271        for (size_t offset = 0; offset + 2 * sizeof(int32_t) <= blobOffset; offset += sizeof(int32_t)) {
 272            int32_t width = 0;
 273            int32_t height = 0;
 274            memcpy(&width, body + offset, sizeof(width));
 275            memcpy(&height, body + offset + sizeof(width), sizeof(height));
 276            if (width < 1 || height < 1 || width > 8192 || height > 8192) {
 277                continue;
 278            }
 279            if ((uint64_t) width * (uint64_t) height * 4 != (uint64_t) size) {
 280                continue;
 281            }
 282
 283            cover->fd = reply->fds[i];
 284            cover->width = (uint32_t) width;
 285            cover->height = (uint32_t) height;
 286            cover->found = true;
 287            FF_DEBUG("The cover is %dx%d, %lld bytes of pixels", width, height, (long long) size);
 288            return true;
 289        }
 290    }
 291    return false;
 292}
 293
 294// ---------------------------------------------------------------------------------------------
 295// Writing the cover out
 296// ---------------------------------------------------------------------------------------------
 297
 298[[gnu::nonnull(2)]]
 299static bool androidWriteAll(FFNativeFD fd, const void* data, size_t length) {
 300    const uint8_t* bytes = data;
 301    while (length > 0) {
 302        const ssize_t written = write(fd, bytes, length);
 303        if (written < 0) {
 304            if (errno == EINTR) {
 305                continue;
 306            }
 307            return false;
 308        }
 309        bytes += (size_t) written;
 310        length -= (size_t) written;
 311    }
 312    return true;
 313}
 314
 315// The callback the encoder writes through. Its contract is that returning false stops it, which is
 316// what makes a full disk come out as no file rather than a truncated one.
 317typedef struct FFMediaAndroidCoverSink {
 318    FFNativeFD fd;
 319    size_t written;
 320    bool failed;
 321} FFMediaAndroidCoverSink;
 322
 323[[gnu::nonnull(1)]]
 324static bool androidCoverSinkWrite(void* userContext, const void* data, size_t size) {
 325    FFMediaAndroidCoverSink* sink = userContext;
 326    if (!androidWriteAll(sink->fd, data, size)) {
 327        sink->failed = true;
 328        return false;
 329    }
 330    sink->written += size;
 331    return true;
 332}
 333
 334// A Bitmap is premultiplied, and artwork without transparency is the same bytes either way -- so
 335// saying which it is costs one pass over the mapping and saves the encoder a bigger one. Declared
 336// premultiplied, AndroidBitmap_compress makes a pass over all 527 KB to unpremultiply pixels it then
 337// finds no alpha in: 41.1 ms against 37.6, for a file that came out byte for byte identical
 338// (sha256 943a6215... on all three of premultiplied, opaque and unpremultiplied). A cover that does
 339// carry transparency keeps the premultiplied description, and its alpha survives.
 340[[gnu::nonnull(1)]] static bool androidCoverIsOpaque(const uint8_t* pixels, size_t length) {
 341    for (size_t i = 3; i < length; i += 4) {
 342        if (pixels[i] != 0xFF) {
 343            return false;
 344        }
 345    }
 346    return true;
 347}
 348
 349// API 30. The pixels are handed over as the mapping itself: the encoder reads them strided, so the
 350// cover never takes a second copy on the way in.
 351FF_ANDROID_REQUIRES_API(30)
 352static bool androidCompressCover(FFNativeFD fd, const void* pixels, uint32_t width, uint32_t height, size_t* outLength) {
 353    // Everything the description needs was already proved by the descriptor's own size, bar one
 354    // thing: the pixels are RGBA8888 and tightly packed, which is what a Bitmap carries, while
 355    // whether there is an alpha worth keeping is not in the size and has to be looked for.
 356    const AndroidBitmapInfo info = {
 357        .width = width,
 358        .height = height,
 359        .stride = width * 4,
 360        .format = ANDROID_BITMAP_FORMAT_RGBA_8888,
 361        .flags = androidCoverIsOpaque(pixels, (size_t) width * height * 4)
 362            ? ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE
 363            : ANDROID_BITMAP_FLAGS_ALPHA_PREMUL,
 364    };
 365
 366    FFMediaAndroidCoverSink sink = { .fd = fd };
 367    const int result = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels, ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS, FF_MEDIA_ANDROID_COVER_QUALITY, &sink, androidCoverSinkWrite);
 368    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
 369        FF_DEBUG("AndroidBitmap_compress returned %d", result);
 370        return false;
 371    }
 372
 373    *outLength = sink.written;
 374    return !sink.failed;
 375}
 376
 377// The descriptor holds the Bitmap's pixels and nothing else: no header, no pixel format and no
 378// length beyond the region's own size. Encoding them is the one thing the session service cannot do
 379// for this module, and the platform has the encoder -- see androidCompressCover above.
 380//
 381// What that encoder costs and produces, measured on this device with a real 363x363 cover (527076
 382// bytes of premultiplied RGBA) and the clock around the call alone:
 383//
 384//   format           quality    output      time
 385//   ---------------  --------   ----------  -------
 386//   PNG              any        203675 B    ~370 ms   quality is ignored, and all four settings
 387//                                                     produced byte-identical files
 388//   WebP lossless    0          146884 B      41 ms
 389//   WebP lossless    100        145064 B      98 ms   the whole effort knob is worth 1.2%
 390//   WebP lossy       80           22060 B     125 ms
 391//   JPEG             90           39973 B      11 ms
 392//
 393// PNG is what the artwork usually arrives as and the worst choice here: the header documents that
 394// quality is ignored for it, and Skia pays for an adaptive filter pass a single image cannot
 395// amortise. Compressing by hand instead -- a deflate stream of stored blocks, which needs no library
 396// at all -- costs 2 ms but writes 527490 bytes, so the platform encoder trades 39 ms for a file 3.6
 397// times smaller.
 398//
 399// Declaring the pixels opaque rather than premultiplied, which the scan in androidCompressCover
 400// makes a truthful thing to say, is worth another 3.5 ms of the 41; the two files are identical, so
 401// what is being bought is only the encoder's pass in search of an alpha that is not there.
 402//
 403// Lossless WebP is the format, for three measured reasons: it is nine times faster than PNG, it is
 404// 3.6 times smaller than what the hand-written encoder produced, and being lossless it keeps alpha --
 405// which matters, because a Bitmap is premultiplied and an encoder that dropped the alpha without
 406// compositing would leave the premultiplied colour behind and turn what was transparent black. Every
 407// reader here takes it as well: the iTerm and kitty logo paths hand the file over as it is for the
 408// terminal to decode, and AImageDecoder, which reads it back inside fastfetch, handles it too.
 409//
 410// TMPDIR is what Termux exports, and it is the one directory the terminal that will draw the logo
 411// can also read; /data/local/tmp is the answer when fastfetch was started by something that exports
 412// nothing. That second one belongs to the shell UID and root, and Termux runs this module as an app
 413// UID for which it is not writable, so the candidates are tested rather than trusted.
 414[[gnu::nonnull(1)]] static const char* androidCoverPath(char* buffer, size_t size) {
 415    const char* directories[] = { getenv("TMPDIR"), "/data/local/tmp" };
 416    for (size_t i = 0; i < sizeof(directories) / sizeof(directories[0]); i++) {
 417        const char* directory = directories[i];
 418        if (directory == nullptr || directory[0] != '/') {
 419            continue;
 420        }
 421        if (access(directory, W_OK) != 0) {
 422            continue;
 423        }
 424        const int length = snprintf(buffer, size, "%s/fastfetch-media-cover-%d.webp", directory, (int) getpid());
 425        if (length <= 0 || (size_t) length >= size) {
 426            continue;
 427        }
 428        return buffer;
 429    }
 430    return nullptr;
 431}
 432
 433// The file belongs to this process, and this is the only place that knows its name: media.c removes
 434// it once the logo is drawn, which is what removeCoverAfterUse asks for, and a half-written file
 435// would otherwise never be removed by anyone.
 436[[gnu::nonnull(1)]] static void androidWriteCover(FFMediaResult* result, int sourceFD, uint32_t width, uint32_t height) {
 437    const off_t size = lseek(sourceFD, 0, SEEK_END);
 438    if (size <= 0 || (uint64_t) size != (uint64_t) width * (uint64_t) height * 4) {
 439        return;
 440    }
 441
 442    // AndroidBitmap_compress is API 30, and the build reaches it through a weak reference, so this
 443    // is a run-time branch rather than a compile-time constant. There is no second encoder behind
 444    // it: AImageDecoder, the only thing on Android that can read a cover back, is API 30 as well, so
 445    // a file written below that would be one whose reader does not exist.
 446    if (FF_ANDROID_API_AT_LEAST(30)) {
 447        void* map = mmap(nullptr, (size_t) size, PROT_READ, MAP_SHARED, sourceFD, 0);
 448        if (map == MAP_FAILED) {
 449            return;
 450        }
 451
 452        char path[256];
 453        const char* file = androidCoverPath(path, sizeof(path));
 454        FF_AUTO_CLOSE_FD FFNativeFD fd = file != nullptr ? open(file, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600) : -1;
 455
 456        size_t length = 0;
 457        const bool written = ffIsValidNativeFD(fd) && androidCompressCover(fd, map, width, height, &length);
 458
 459        munmap(map, (size_t) size);
 460
 461        if (written) {
 462            ffStrbufSetS(&result->cover, file);
 463            result->removeCoverAfterUse = true;
 464            FF_DEBUG("The %ux%u cover was written to %s, %zu bytes", width, height, file, (size_t) length);
 465        } else if (file != nullptr) {
 466            ffRemoveFile(file);
 467        }
 468    }
 469}
 470
 471// ---------------------------------------------------------------------------------------------
 472// The session
 473// ---------------------------------------------------------------------------------------------
 474
 475// Every transaction code this module sends, resolved from the device's own jar. They are resolved
 476// together, in one walk, because that is the cheap way to get them: the walk is dominated by the
 477// dex's type table, and the two stub classes sit in the same entry of every framework.jar seen so far
 478// (classes2.dex on Android 11 and 13, classes3.dex on Android 16), so one pass answers all four.
 479// Asking one at a time would repeat that pass four times -- see common/android/dex.h.
 480typedef struct FFMediaAndroidCodes {
 481    int32_t getSessions;
 482    int32_t getPackageName;
 483    int32_t getMetadata;
 484    int32_t getPlaybackState;
 485} FFMediaAndroidCodes;
 486
 487static const char* androidResolveCodes(FFMediaAndroidCodes* codes) {
 488    const FFDexStaticIntRequest requests[] = {
 489        { FF_MEDIA_ANDROID_SESSIONS_STUB, FF_MEDIA_ANDROID_GET_SESSIONS, &codes->getSessions },
 490        { FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_PACKAGE_NAME, &codes->getPackageName },
 491        { FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_METADATA, &codes->getMetadata },
 492        { FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_PLAYBACK_STATE, &codes->getPlaybackState },
 493    };
 494
 495    const char* error = ffDexStaticInts(FF_MEDIA_ANDROID_JAR, requests, ARRAY_SIZE(requests));
 496    if (error != nullptr) {
 497        FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_MEDIA_ANDROID_JAR, error);
 498        return error;
 499    }
 500
 501    // All four are codes the module cannot answer without. The reader leaves a code it could not find
 502    // at the sentinel instead of judging what that costs the caller, so the judgement is made here.
 503    if (codes->getSessions == FF_DEX_STATIC_INT_UNRESOLVED
 504        || codes->getPackageName == FF_DEX_STATIC_INT_UNRESOLVED
 505        || codes->getMetadata == FF_DEX_STATIC_INT_UNRESOLVED
 506        || codes->getPlaybackState == FF_DEX_STATIC_INT_UNRESOLVED) {
 507        FF_DEBUG("Unresolved transaction codes: getSessions=%d, getPackageName=%d, getMetadata=%d, getPlaybackState=%d",
 508            (int) codes->getSessions, (int) codes->getPackageName, (int) codes->getMetadata, (int) codes->getPlaybackState);
 509        return "The session service does not declare every method this module calls";
 510    }
 511    return nullptr;
 512}
 513
 514// Every getter on ISessionController takes no argument, so a call is the interface token and nothing
 515// else; `flags` is passed through because one of them needs TF_ACCEPT_FDS.
 516[[gnu::nonnull(1, 5)]] static const char* androidCall(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, FFBinderReply* reply) {
 517    uint8_t request[FF_MEDIA_ANDROID_REQUEST_SIZE];
 518    FFBinderParcel parcel = ffBinderParcelCreate(request, sizeof(request));
 519    ffBinderParcelPutInterfaceToken(&parcel, FF_MEDIA_ANDROID_CONTROLLER_DESCRIPTOR);
 520
 521    const char* error = ffBinderTransact(binder, handle, code, flags, &parcel, reply);
 522    if (error != nullptr) {
 523        return error;
 524    }
 525    if (ffBinderReplyIsStatus(reply)) {
 526        // onTransact() answered with a raw status_t instead of a parcel. -74 is UNKNOWN_TRANSACTION,
 527        // which is what a code this build of the interface does not have comes back as.
 528        return ffBinderReadI32(reply->data, reply->size, 0) == -74
 529            ? "The session does not have that method"
 530            : "The session rejected the request";
 531    }
 532
 533    // Every reply starts with the exception code AIDL writes, so nothing past it is read before this
 534    // has been checked.
 535    const int32_t exception = ffBinderReadI32(reply->data, reply->size, 0);
 536    if (exception != 0) {
 537        FF_DEBUG("Transaction %u came back as exception %d over %zu bytes", code, exception, reply->size);
 538        return "The session raised an exception";
 539    }
 540
 541    return nullptr;
 542}
 543
 544[[gnu::nonnull(1, 4, 5, 6, 7)]]
 545static const char* androidReadMetadataEntry(FFMediaAndroidReader* reader, size_t bundleEnd, int32_t type, const FFstrbuf* key, FFMediaResult* result, const FFBinderReply* reply, FFMediaAndroidCover* cover) {
 546    switch (type) {
 547        case FF_MEDIA_ANDROID_VAL_NULL:
 548            return nullptr;
 549
 550        case FF_MEDIA_ANDROID_VAL_STRING:
 551        case FF_MEDIA_ANDROID_VAL_CHAR_SEQUENCE: {
 552            FF_STRBUF_AUTO_DESTROY value = ffStrbufCreate();
 553            if (!androidReadString16(reader, &value)) {
 554                return "The metadata carries a truncated string";
 555            }
 556            if (value.length == 0) {
 557                return nullptr;
 558            }
 559
 560            // DISPLAY_TITLE and DISPLAY_SUBTITLE are the fallbacks Android documents for a session
 561            // whose structured metadata is incomplete -- a radio stream, typically -- and an empty
 562            // TITLE is exactly the case they exist for.
 563            if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_TITLE)) {
 564                ffStrbufSet(&result->song, &value);
 565            } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_DISPLAY_TITLE)) {
 566                if (result->song.length == 0) {
 567                    ffStrbufSet(&result->song, &value);
 568                }
 569            } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_ARTIST)) {
 570                ffStrbufSet(&result->artist, &value);
 571            } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_DISPLAY_SUBTITLE)) {
 572                if (result->artist.length == 0) {
 573                    ffStrbufSet(&result->artist, &value);
 574                }
 575            } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_ALBUM)) {
 576                ffStrbufSet(&result->album, &value);
 577            } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_MEDIA_URI)) {
 578                ffStrbufSet(&result->url, &value);
 579            }
 580            return nullptr;
 581        }
 582
 583        case FF_MEDIA_ANDROID_VAL_LONG: {
 584            int64_t value = 0;
 585            if (!androidReadI64(reader, &value)) {
 586                return "The metadata carries a truncated long";
 587            }
 588            // METADATA_KEY_DURATION is milliseconds, where MPRIS answers microseconds -- the one
 589            // field that has to be read differently on each platform.
 590            if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_DURATION) && value > 0 && value <= (int64_t) UINT32_MAX) {
 591                result->length = (uint32_t) value;
 592            }
 593            return nullptr;
 594        }
 595
 596        case FF_MEDIA_ANDROID_VAL_INTEGER: {
 597            int32_t value = 0;
 598            if (!androidReadI32(reader, &value)) {
 599                return "The metadata carries a truncated int";
 600            }
 601            return nullptr;
 602        }
 603
 604        case FF_MEDIA_ANDROID_VAL_PARCELABLE: {
 605            // Parcel.writeValue length-prefixes a Parcelable with the byte size of everything that
 606            // follows, and that size is what makes an unknown class skippable: the walk resumes at the
 607            // end of the payload instead of having to understand it.
 608            int32_t payloadLength = 0;
 609            if (!androidReadI32(reader, &payloadLength) || payloadLength < 0) {
 610                return "The metadata carries a malformed parcelable";
 611            }
 612            const size_t payloadEnd = reader->position + (size_t) payloadLength;
 613            if (payloadEnd > bundleEnd) {
 614                return "The metadata carries a parcelable past the end of the bundle";
 615            }
 616
 617            FF_STRBUF_AUTO_DESTROY className = ffStrbufCreate();
 618            if (!androidReadString16(reader, &className)) {
 619                return "The metadata carries a truncated parcelable name";
 620            }
 621
 622            if (ffStrbufEqualS(&className, FF_MEDIA_ANDROID_CLASS_BITMAP)) {
 623                uint32_t priority = 0;
 624                if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_ART)) {
 625                    priority = FF_MEDIA_ANDROID_COVER_ART;
 626                } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_ALBUM_ART)) {
 627                    priority = FF_MEDIA_ANDROID_COVER_ALBUM_ART;
 628                } else if (ffStrbufEqualS(key, FF_MEDIA_ANDROID_KEY_DISPLAY_ICON)) {
 629                    priority = FF_MEDIA_ANDROID_COVER_DISPLAY_ICON;
 630                }
 631
 632                if (priority > cover->priority) {
 633                    FFMediaAndroidCover candidate = { .fd = -1 };
 634                    if (androidTakeBitmap(reader->data + reader->position, payloadEnd - reader->position, reply, &candidate)) {
 635                        candidate.priority = priority;
 636                        *cover = candidate;
 637                    }
 638                }
 639            }
 640
 641            reader->position = payloadEnd;
 642            return nullptr;
 643        }
 644
 645        default:
 646            // Anything else -- a map, a list, an array -- would have to be walked by shape to be
 647            // skipped, and a MediaMetadata Bundle does not carry one. Stopping here is what keeps the
 648            // walk from reading a wrong number of bytes and carrying on with nonsense.
 649            FF_DEBUG("The metadata carries an unsupported value type %d", type);
 650            return "The metadata carries a value this build cannot read";
 651    }
 652}
 653
 654[[gnu::nonnull(1, 2, 3, 4)]]
 655static const char* androidReadMetadataBundle(FFMediaAndroidReader* reader, FFMediaResult* result, const FFBinderReply* reply, FFMediaAndroidCover* cover) {
 656    int32_t bundleLength = 0;
 657    if (!androidReadI32(reader, &bundleLength) || bundleLength <= 0) {
 658        FF_DEBUG("The metadata bundle length is %d, which cannot hold a bundle", (int) bundleLength);
 659        return "The metadata bundle is empty";
 660    }
 661    const size_t bundleEnd = reader->position + (size_t) bundleLength;
 662    if (bundleEnd > reader->size) {
 663        return "The metadata bundle runs past the end of the reply";
 664    }
 665
 666    int32_t magic = 0;
 667    if (!androidReadI32(reader, &magic) || (uint32_t) magic != FF_MEDIA_ANDROID_BUNDLE_MAGIC) {
 668        FF_DEBUG("The metadata magic is 0x%08x instead of 0x%08x", (unsigned) magic, FF_MEDIA_ANDROID_BUNDLE_MAGIC);
 669        return "The metadata is not a bundle";
 670    }
 671    int32_t count = 0;
 672    if (!androidReadI32(reader, &count)) {
 673        FF_DEBUG("The metadata bundle is truncated before its entry count");
 674        return "The metadata bundle has no entry count";
 675    }
 676
 677    FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
 678    int32_t read = 0;
 679    for (; read < count && reader->position < bundleEnd; read++) {
 680        if (!androidReadString16(reader, &key)) {
 681            return "The metadata carries a truncated key";
 682        }
 683        int32_t type = 0;
 684        if (!androidReadI32(reader, &type)) {
 685            return "The metadata carries a value without a type";
 686        }
 687        const char* error = androidReadMetadataEntry(reader, bundleEnd, type, &key, result, reply, cover);
 688        if (error != nullptr) {
 689            return error;
 690        }
 691    }
 692
 693    // The count in the header is the check on the walk: a discrepancy means a type this build reads the
 694    // wrong width for, and a partially read title is worse than none.
 695    if (read != count) {
 696        FF_DEBUG("The metadata bundle declared %d entries but only %d could be read", count, read);
 697        return "The metadata bundle did not read back in full";
 698    }
 699
 700    return nullptr;
 701}
 702
 703// PlaybackState.STATE_*, which is what getPlaybackState answers with. The three names MPRIS uses are
 704// kept identical -- Playing, Paused, Stopped -- so that a format string written for the Linux or the
 705// Windows implementation keeps working.
 706static const char* androidPlaybackStateName(int32_t state) {
 707    switch (state) {
 708        case 0: return "None";
 709        case 1: return "Stopped";
 710        case 2: return "Paused";
 711        case 3: return "Playing";
 712        case 4: return "FastForwarding";
 713        case 5: return "Rewinding";
 714        case 6: return "Buffering";
 715        case 7: return "Error";
 716        case 8: return "Connecting";
 717        case 9: return "SkippingToPrevious";
 718        case 10: return "SkippingToNext";
 719        case 11: return "SkippingToQueueItem";
 720        default: return "Unknown";
 721    }
 722}
 723
 724static uint32_t androidPlaybackStateScore(int32_t state) {
 725    // Playing first, then the states that are on their way to it, then paused. A session that is
 726    // stopped or has no state at all is only reported when nothing else is there.
 727    switch (state) {
 728        case 3: return 4;                                                   // PLAYING
 729        case 4: case 5: case 6: case 8: case 9: case 10: case 11: return 3; // moving, buffering, connecting
 730        case 2: return 2;                                                   // PAUSED
 731        case 1: case 7: return 1;                                           // STOPPED, ERROR
 732        default: return 0;
 733    }
 734}
 735
 736// SystemClock.elapsedRealtime(), which is the clock PlaybackState.updateTime is stamped with. It is
 737// CLOCK_BOOTTIME and not CLOCK_MONOTONIC: a phone suspends with music playing often enough that the
 738// two differ by hours, and the difference would be read as a position that never advances.
 739static int64_t androidElapsedRealtime(void) {
 740    struct timespec now = {};
 741    if (clock_gettime(CLOCK_BOOTTIME, &now) != 0) {
 742        return 0;
 743    }
 744    return (int64_t) now.tv_sec * 1000 + (int64_t) (now.tv_nsec / 1000000);
 745}
 746
 747// The reply is walked only as far as the speed: everything past it is a typed list of custom actions,
 748// an item id, a CharSequence and a Bundle, none of which this module reports. `length` therefore has
 749// to have been read already -- the clamping below needs it -- which is why the metadata pass runs
 750// first.
 751[[gnu::nonnull(1, 2)]] static const char* androidReadPlaybackState(FFMediaAndroidReader* reader, FFMediaResult* result) {
 752    int32_t state = 0;
 753    if (!androidReadI32(reader, &state) || state == 0) {
 754        return nullptr; // the session has no state yet
 755    }
 756
 757    int64_t position = 0;
 758    if (!androidReadI64(reader, &position)) {
 759        return "The playback state is truncated";
 760    }
 761
 762    float speed = 0;
 763    if (reader->position + sizeof(float) > reader->size) {
 764        return "The playback state is truncated";
 765    }
 766    memcpy(&speed, reader->data + reader->position, sizeof(speed));
 767    reader->position += sizeof(speed);
 768
 769    int64_t updateTime = 0;
 770    if (!androidReadI64(reader, &updateTime)) {
 771        return "The playback state is truncated";
 772    }
 773
 774    // `position` is the position *at* updateTime, so a snapshot taken now has to be advanced by the
 775    // time that has passed since -- otherwise a player that only refreshes its state once per track
 776    // would report the same position for the whole of it. Only a playing session advances: a paused
 777    // one is exactly where it says it is.
 778    int64_t current = position;
 779    if (state == 3 && speed > 0) {
 780        const int64_t now = androidElapsedRealtime();
 781        if (now > updateTime) {
 782            current += (int64_t) ((double) (now - updateTime) * (double) speed);
 783        }
 784    }
 785
 786    if (current < 0) {
 787        current = 0;
 788    }
 789    if (result->length > 0 && current > (int64_t) result->length) {
 790        current = result->length;
 791    }
 792    if (current > 0 && current <= (int64_t) UINT32_MAX) {
 793        result->position = (uint32_t) current;
 794    }
 795
 796    ffStrbufSetS(&result->status, androidPlaybackStateName(state));
 797    return nullptr;
 798}
 799
 800static void androidReadPackageName(FFBinder* binder, uint32_t token, uint32_t transaction, FFMediaResult* result) {
 801    FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
 802    const char* error = androidCall(binder, token, transaction, 0, &reply);
 803    if (error != nullptr) {
 804        FF_DEBUG("getPackageName failed: %s", error);
 805        return;
 806    }
 807
 808    FFMediaAndroidReader reader = { .data = reply.data, .size = reply.size, .position = 4 };
 809    if (!androidReadString16(&reader, &result->playerId)) {
 810        return;
 811    }
 812
 813    // `player` wants the name the app shows rather than the name it is installed under, and the
 814    // package name is only the fallback. The label comes out of the APK's own resource table, which
 815    // the package manager points at with `sourceDir` and `labelRes`: the table is *stored* rather
 816    // than deflated -- the platform expects to mmap it -- so resolving an id out of it needs no
 817    // decompressor. What keeps the package name are the apps whose label is a literal string in
 818    // their manifest instead of a resource reference, since their reply carries no id at all; that
 819    // is also what the notification and the settings screen fall back to. See doc/app-label.md.
 820    ffStrbufSet(&result->player, &result->playerId);
 821    const char* labelError = ffAndroidGetAppLabel(result->playerId.chars, &result->player);
 822    if (labelError != nullptr) {
 823        FF_DEBUG("The label of \"%s\" could not be resolved: %s", result->playerId.chars, labelError);
 824    }
 825}
 826
 827static void androidReadMetadata(FFBinder* binder, uint32_t token, uint32_t transaction, FFMediaResult* result, bool saveCover) {
 828    FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
 829
 830    // TF_ACCEPT_FDS is not a hint and not optional here: the artwork arrives as a descriptor, and a
 831    // request that did not ask for one is answered with BR_FAILED_REPLY -- which reads like a service
 832    // that is not running rather than like a missing flag. It is passed unconditionally, because the
 833    // alternative is a failure that depends on whether the player happened to publish artwork.
 834    const char* error = androidCall(binder, token, transaction, TF_ACCEPT_FDS, &reply);
 835
 836    FFMediaAndroidCover cover = { .fd = -1 };
 837    if (error != nullptr) {
 838        FF_DEBUG("getMetadata failed: %s", error);
 839    } else if (ffBinderReadI32(reply.data, reply.size, 4) != 0) {
 840        FFMediaAndroidReader reader = { .data = reply.data, .size = reply.size, .position = 8 };
 841        const char* parseError = androidReadMetadataBundle(&reader, result, &reply, &cover);
 842        if (parseError != nullptr) {
 843            FF_DEBUG("The metadata bundle could not be read: %s", parseError);
 844        }
 845    }
 846
 847    if (cover.found && saveCover) {
 848        androidWriteCover(result, cover.fd, cover.width, cover.height);
 849    }
 850
 851    // Every descriptor the reply carried is this side's to close, the cover's included: the pixels
 852    // have been read out by now, and the one no cover came from would otherwise leak.
 853    for (uint32_t i = 0; i < reply.fdCount; i++) {
 854        close(reply.fds[i]);
 855    }
 856}
 857
 858static void androidReadSession(FFBinder* binder, uint32_t token, const FFMediaAndroidCodes* codes, FFMediaResult* result, bool saveCover) {
 859    androidReadPackageName(binder, token, (uint32_t) codes->getPackageName, result);
 860
 861    // The length has to be known before the position is placed, so the metadata comes first.
 862    androidReadMetadata(binder, token, (uint32_t) codes->getMetadata, result, saveCover);
 863
 864    FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
 865    const char* error = androidCall(binder, token, (uint32_t) codes->getPlaybackState, 0, &reply);
 866    if (error == nullptr && ffBinderReadI32(reply.data, reply.size, 4) != 0) {
 867        FFMediaAndroidReader reader = { .data = reply.data, .size = reply.size, .position = 8 };
 868        error = androidReadPlaybackState(&reader, result);
 869    }
 870    if (error != nullptr) {
 871        FF_DEBUG("getPlaybackState failed: %s", error);
 872    }
 873}
 874
 875// ---------------------------------------------------------------------------------------------
 876
 877// Everything that goes through the media_session service. A reason is returned when it came up empty;
 878// a song in `media` means it did not.
 879//
 880// There is deliberately no uid test in front of this. `getSessions` is refused for an app UID, but that
 881// is the service's decision to make and it is not the only way in -- a notification listener is let
 882// through too, an OEM can loosen the check, a release can change it -- so the module asks and reads the
 883// answer. That costs one transaction, and it keeps the module working wherever the check happens to
 884// pass, instead of hard-coding today's policy into the client. What the request has to carry for the
 885// check to be consulted at all is the ComponentName: see the request build below.
 886static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCover) {
 887    // Resolved before the binder is opened, so that a jar which does not declare the methods says so
 888    // rather than the module failing later with a message about the transport. The reader's own
 889    // reason is what is returned: a jar that cannot be read, a class it does not define and a field
 890    // the class does not declare are three different things.
 891    FFMediaAndroidCodes codes = {};
 892    const char* error = androidResolveCodes(&codes);
 893    if (error != nullptr) {
 894        return error;
 895    }
 896
 897    [[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
 898    error = ffBinderOpen(&binder);
 899    if (error != nullptr) {
 900        return error;
 901    }
 902
 903    [[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
 904    error = ffBinderLookupService(&binder, FF_MEDIA_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
 905    if (error != nullptr) {
 906        FF_DEBUG("Looking up \"%s\" failed: %s", FF_MEDIA_ANDROID_SERVICE, error);
 907        return error;
 908    }
 909    FF_DEBUG("The \"%s\" service is handle %u, getSessions is transaction %d", FF_MEDIA_ANDROID_SERVICE, service.handle, codes.getSessions);
 910
 911    // A phone can have several sessions at once -- a music player and a video one, say -- and only one
 912    // of them is the answer. Which one is decided first, by state alone, so that the metadata and the
 913    // artwork are fetched exactly once, for the winner.
 914    uint32_t tokens[FF_BINDER_MAX_HANDLES];
 915    uint32_t tokenCount = 0;
 916    // The descriptors the reply below delivers, held outside its block because the reply itself does
 917    // not outlive it: pointing `acquired` into `reply.fds` leaves it reading whatever the next call
 918    // puts on that stack, which closes a descriptor that is not ours and leaks the one that is.
 919    // Declared above the cleanup variable for the same reason `tokens[]` is -- a local declared
 920    // after one has its lifetime ended before that cleanup runs, which is what a sanitizer sees.
 921    int32_t fds[FF_BINDER_MAX_FDS] = {};
 922    uint32_t fdCount = 0;
 923    // Raised as soon as ffBinderTransact() has replied, because from that point on it is holding a
 924    // strong reference to every handle it collected and the kernel has installed every descriptor it
 925    // delivered into our own fd table. Any early return past that line has to give them back, so one
 926    // cleanup attribute does it rather than a release call repeated at each exit.
 927    //
 928    // `replied` is what decides: ffBinderTransact() acquires the handles before it can fail -- it
 929    // returns an error when the reply did not fit the caller buffer, and the tail that carries
 930    // BC_ACQUIRE runs regardless -- so releasing without the flag would drop one reference too many.
 931    bool replied = false;
 932    [[gnu::cleanup(ffBinderAcquiredRelease)]] FFBinderAcquired acquired = { .binder = &binder, .replied = &replied };
 933    {
 934        uint8_t request[FF_MEDIA_ANDROID_REQUEST_SIZE];
 935        FFBinderParcel parcel = ffBinderParcelCreate(request, sizeof(request));
 936        ffBinderParcelPutInterfaceToken(&parcel, FF_MEDIA_ANDROID_SESSIONS_DESCRIPTOR);
 937
 938        // The ComponentName is not a filter and not decoration: it decides which branch of the
 939        // service's permission check runs, and with a null one the branch this module needs is never
 940        // reached. MediaSessionService.enforceMediaPermissions() admits the shell and root, a holder of
 941        // android.permission.MEDIA_CONTENT_CONTROL, and a UID that owns an enabled notification
 942        // listener -- but the last one is reached through isEnabledNotificationListener(compName, uid),
 943        // which answers false for a null compName by construction. A null therefore asks for every
 944        // session only on behalf of a UID that was going to be admitted anyway.
 945        //
 946        // Measured on the test device, one binary run by three UIDs: the shell gets the session list
 947        // for every shape below; the app UID that owns an enabled listener gets -1 EX_SECURITY for a
 948        // null ComponentName and the session list for a non-null one; an app UID that owns none gets
 949        // -1 EX_SECURITY for both. Two more facts
 950        // come out of the same run: the class inside the ComponentName is never validated -- a name no
 951        // package declares and an empty string are answered exactly like a real one -- and the package
 952        // has to be one the calling uid owns, because the service's own enforcePackageName() rejects
 953        // anything else with EX_ILLEGAL_ARGUMENT ("packageName does not belong to the calling uid")
 954        // before the permission check is reached.
 955        //
 956        // So the caller declares its own package and the class is left empty rather than invented:
 957        // this is a standalone binary with no component of its own to name, and naming one the device
 958        // does not have would be a claim that cannot be backed. A build whose package cannot be
 959        // derived sends the null request, which is what this did before.
 960        char package[FF_MEDIA_ANDROID_PACKAGE_SIZE];
 961        if (ffAndroidGetOwnPackage(package, sizeof(package))) {
 962            ffBinderParcelPutI32(&parcel, 1);
 963            ffBinderParcelPutString16(&parcel, package);
 964            ffBinderParcelPutString16(&parcel, "");
 965        } else {
 966            FF_DEBUG("Cannot determine the package of this process, so the request declares no component");
 967            ffBinderParcelPutI32(&parcel, 0);
 968        }
 969        ffBinderParcelPutI32(&parcel, 0); // userId: the single user a phone has
 970
 971        FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
 972        error = ffBinderTransact(&binder, service.handle, (uint32_t) codes.getSessions, 0, &parcel, &reply);
 973        replied = true;
 974        if (error != nullptr) {
 975            return error;
 976        }
 977        if (ffBinderReplyIsStatus(&reply)) {
 978            return "The media session service rejected the request";
 979        }
 980        const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
 981        if (exception != 0) {
 982            // -1 is EX_SECURITY, which is what a UID the service will not read sessions for gets. The
 983            // number goes to the debug log rather than into the message: what tells the two cases apart
 984            // for the user is which UID is asking. On an app UID the missing privilege is named,
 985            // because it is one the user can grant -- the request below already declares the caller,
 986            // so whatever is left is the listener the UID does not own.
 987            FF_DEBUG("getSessions came back as exception %d%s", exception, exception == -1 ? " (EX_SECURITY)" : "");
 988            return ffAndroidIsRootOrShell(instance.state.platform.uid)
 989                ? "The media session service refused the request"
 990                : "Reading the media session from an app UID needs an enabled notification listener, which this UID does not have";
 991        }
 992
 993        // The tokens arrive as flat binder objects, which ffBinderTransact() collects in payload order
 994        // -- the same order the descriptors of a metadata reply arrive in, which is what makes the two
 995        // line up without walking the list itself.
 996        tokenCount = reply.handleCount;
 997        if (tokenCount > FF_BINDER_MAX_HANDLES) {
 998            tokenCount = FF_BINDER_MAX_HANDLES;
 999        }
1000        for (uint32_t i = 0; i < tokenCount; i++) {
1001            tokens[i] = reply.handles[i];
1002        }
1003        acquired.handles = tokens;
1004        acquired.handleCount = tokenCount;
1005        // Every descriptor the reply delivered is ours to close. The art of a session is read through
1006        // one of them, and reading it happens over the fd number rather than over the file, so closing
1007        // it on the way out does not cut the read short.
1008        fdCount = reply.fdCount;
1009        memcpy(fds, reply.fds, sizeof(fds));
1010        acquired.fds = fds;
1011        acquired.fdCount = fdCount;
1012    }
1013
1014    if (tokenCount == 0) {
1015        FF_DEBUG("The media_session service returned no session tokens");
1016        return "No active media session";
1017    }
1018
1019    uint32_t best = 0;
1020    uint32_t bestScore = 0;
1021    for (uint32_t i = 0; i < tokenCount; i++) {
1022        FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
1023        if (androidCall(&binder, tokens[i], (uint32_t) codes.getPlaybackState, 0, &reply) != nullptr) {
1024            continue;
1025        }
1026        // The presence marker first: a session with no state yet answers a null parcelable, and its
1027        // state field is not there to be read.
1028        const int32_t state = ffBinderReadI32(reply.data, reply.size, 4) != 0 ? ffBinderReadI32(reply.data, reply.size, 8) : 0;
1029        const uint32_t score = androidPlaybackStateScore(state);
1030        if (score > bestScore) {
1031            bestScore = score;
1032            best = i;
1033        }
1034    }
1035
1036    androidReadSession(&binder, tokens[best], &codes, media, saveCover);
1037
1038    // The tokens were acquired by ffBinderTransact() and are given back by the cleanup attribute above.
1039    return nullptr;
1040}
1041
1042// ---------------------------------------------------------------------------------------------
1043
1044// The MPRIS implementation in media_linux.c, whose entry point is renamed on Android because this file
1045// owns ffDetectMediaImpl -- the same arrangement as wallpaper_linux.c and terminalfont_linux.c.
1046void ffDetectMediaLinuxImpl(FFMediaResult* media, bool saveCover);
1047
1048void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) {
1049    // The binder route below reads Android's own now-playing session, which is the one the phone is
1050    // playing only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs a
1051    // real compositor on top of the device, and what is playing there belongs to that desktop, not to
1052    // Android -- so that case is handed to the Linux implementation, which knows the MPRIS bus. Only
1053    // one of the two answers, never merged.
1054    const FFDisplayServerResult* wm = ffConnectDisplayServer();
1055    if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
1056        FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars);
1057        ffDetectMediaLinuxImpl(media, saveCover);
1058        return;
1059    }
1060
1061    const char* error = androidDetectMediaSession(media, saveCover);
1062    if (media->song.length > 0) {
1063        return; // the phone's own now-playing session answered
1064    }
1065
1066    // Nothing came out of the media_session service, so try the session bus. That is worth trying even
1067    // with SurfaceFlinger on screen: the service refuses an app UID outright, and a player inside a
1068    // chroot is not an Android media session at all, so nothing else here would ever see it.
1069    //
1070    // The reason is copied out first, because the fallback writes to media->error itself.
1071    char sessionError[160] = "";
1072    if (error != nullptr) {
1073        snprintf(sessionError, sizeof(sessionError), "%s", error);
1074    }
1075    FF_DEBUG("The media session gave nothing (%s), falling back to MPRIS", sessionError[0] != '\0' ? sessionError : "no session");
1076
1077    // Artwork without a song to go with it: the file belongs to the track MPRIS is about to report, so
1078    // it is removed here rather than at exit, where media.c would by then have forgotten the path.
1079    if (media->cover.length > 0) {
1080        ffRemoveFile(media->cover.chars);
1081        ffStrbufClear(&media->cover);
1082        media->removeCoverAfterUse = false;
1083    }
1084
1085    ffStrbufClear(&media->error);
1086    ffDetectMediaLinuxImpl(media, saveCover);
1087
1088    // The phone is what was asked about, so its reason is the one to print: it names something a user
1089    // can act on, while "Failed to connect to DBus" only explains why the second opinion was never
1090    // taken -- and most phones have no session bus at all, which would make that the usual answer.
1091    // MPRIS never reports anything when it merely finds no player, so a silent fallback here means
1092    // nothing is playing anywhere.
1093    if (media->song.length == 0) {
1094        ffStrbufSetS(&media->error, sessionError[0] != '\0' ? sessionError : "No active media session");
1095    }
1096}