feat: hide an Android BIOS folder from the gallery

An install on Android leaves an empty .nomedia in the BIOS folder. MediaProvider hides a folder holding one and every folder below it (FileUtils.java:1587-1629, ModernMediaScanner.java:845-846, a183e2b9), and RetroArch writes none: the samples, overlays and videos of system/ showed in the gallery and music apps. --check and --dry-run write nothing.
This commit is contained in:
Abdessamad Derraz committed 2026-11-09 05:41:49 +01:00
1 parent ae5b6219bb
commit ec407dfbdd
7 files changed
+113 -10

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "bba0f7cd7677fb7f4d767504412843cf3809c03b91808c7c7772f17758893d50"
$defaultInstallSha256 = "192ecdd781447b69967d5d335911fb34887757ee1bf9b6c000ad68a62b17de4b"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+27
View File
@@ -3132,6 +3132,29 @@ def fold_case_twins(files: list[dict]) -> tuple[list[dict], list[str]]:
return kept, folded
# Android's media scanner marks a folder holding a .nomedia file hidden, and
# every folder below it (MediaProvider a183e2b9, FileUtils.java:1587-1629
# isDirectoryHidden, ModernMediaScanner.java:845-846 and 1017-1020). Without
# one, the samples, overlays and videos of a BIOS folder in shared storage
# showed in the phone's gallery and music apps. RetroArch writes none
# (libretro-common/file/file_path_io.c:194 only skips one).
NOMEDIA = ".nomedia"
def hide_from_media_scanner(folder: Path) -> bool:
"""Leave an empty .nomedia in folder, True when this call wrote it."""
marker = folder / NOMEDIA
if not _is_dir(folder) or _exists(marker):
return False
try:
marker.touch()
except OSError as exc:
print(f" {NOMEDIA} not written in {shown_path(folder)}: {exc.strerror}",
file=sys.stderr)
return False
return True
def installed_cores(dirs: list[Path]) -> list[str]:
"""The cores present in these directories, by the name the buildbot uses.
@@ -16343,6 +16366,10 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
failures.append((where, f"{NOT_KEPT} ({reason})"))
total_errors += 1
if os_type == "android" and not args.check and hide_from_media_scanner(bios_path):
print(f" Added {NOMEDIA} to {shown_path(bios_path)}: the gallery and music "
"apps leave its images, sounds and videos out")
# Standalone copies
if (
manifest.get("standalone_copies")
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="bba0f7cd7677fb7f4d767504412843cf3809c03b91808c7c7772f17758893d50"
DEFAULT_INSTALL_SHA256="192ecdd781447b69967d5d335911fb34887757ee1bf9b6c000ad68a62b17de4b"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1125268,
"sha256": "bba0f7cd7677fb7f4d767504412843cf3809c03b91808c7c7772f17758893d50",
"size": 1126407,
"sha256": "192ecdd781447b69967d5d335911fb34887757ee1bf9b6c000ad68a62b17de4b",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1178895,
"sha256": "814d80f123e59f5192631842d53a4808bf373599ced857f4622612ffbc25322b"
"size": 1180034,
"sha256": "14c4e4b762ec84fe30d2cf0baed58d1022c3423e05118b43dbccfbaff5e4d84a"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1125268,
"sha256": "bba0f7cd7677fb7f4d767504412843cf3809c03b91808c7c7772f17758893d50",
"size": 1126407,
"sha256": "192ecdd781447b69967d5d335911fb34887757ee1bf9b6c000ad68a62b17de4b",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1125268,
"sha256": "bba0f7cd7677fb7f4d767504412843cf3809c03b91808c7c7772f17758893d50",
"size": 1126407,
"sha256": "192ecdd781447b69967d5d335911fb34887757ee1bf9b6c000ad68a62b17de4b",
"executable": false
},
{
+64
View File
@@ -0,0 +1,64 @@
"""What an install does for the device and file system it lands on."""
from __future__ import annotations
import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache
import subprocess
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
sys.path.insert(0, str(REPO_ROOT / "tests"))
from test_install_selection import FakeRepository # noqa: E402
class AndroidMediaScanner(unittest.TestCase):
"""Without a .nomedia the BIOS folder's samples, overlays and videos
showed in the phone's gallery and music apps."""
def setUp(self):
self.repo = FakeRepository().__enter__()
self.addCleanup(self.repo.__exit__)
def _run(self, os_name: str, *options: str):
proc = subprocess.run(self.repo.command("--yes", "--no-input", *options),
env={**self.repo.env(), "RETROBIOS_OS": os_name},
capture_output=True, text=True, timeout=60,
stdin=subprocess.DEVNULL)
return self.repo, proc
def test_an_android_install_writes_one(self):
repo, proc = self._run("android")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
marker = repo.dest / ".nomedia"
self.assertTrue(marker.is_file())
self.assertEqual(marker.stat().st_size, 0)
self.assertIn("Added .nomedia", proc.stdout)
def test_a_second_run_leaves_it_and_says_nothing(self):
self._run("android")
_, proc = self._run("android")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertNotIn("Added .nomedia", proc.stdout)
# The marker is not a file of the list: the check finds all in place.
_, check = self._run("android", "--check")
self.assertEqual(check.returncode, 0, check.stdout + check.stderr)
def test_a_check_and_a_dry_run_write_nothing(self):
for options in (("--check",), ("--dry-run",)):
with self.subTest(options=options):
self._run("android", *options)
self.assertFalse((self.repo.dest / ".nomedia").exists())
def test_other_systems_get_none(self):
repo, proc = self._run("linux")
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertFalse((repo.dest / ".nomedia").exists())
if __name__ == "__main__":
unittest.main()
+5
View File
@@ -101,6 +101,11 @@ Fill it from a computer or point `--from-pack` at your ZIP: [Without a network](
`--from` copies BIOS files from an old card or backup:
[Copy from an old card](installer.md#copy-from-an-old-card).
## BIOS pictures or sounds in my phone's gallery?
An empty file named `.nomedia` in the BIOS folder hides them. The installer
adds it on Android: [Android](installer.md#android).
## My setup is not in the list
- Name your emulator's BIOS folder with `--dest`.
+7
View File
@@ -1461,6 +1461,13 @@ elsewhere. A custom BIOS directory is still named directly:
python install.py --platform retroarch --dest /storage/emulated/0/RetroArch/system
```
An install on Android leaves an empty `.nomedia` in the BIOS folder. Android's
media scanner hides a folder holding one, and every folder below it
(MediaProvider, `FileUtils.java` `isDirectoryHidden`), so the samples,
overlays and videos of the BIOS folder stay out of the gallery and music
apps. RetroArch writes none. A run that finds one already there leaves it,
and `--check` and `--dry-run` write nothing.
## On a handheld { #on-a-handheld-the-retrobios-port }
RetroBIOS also runs on the handheld itself, in the