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}