droiddeck-steam-shortcuts and AddedGames.accountConfig took the account
marked "MostRecent" in loginusers.vdf, else the only userdata folder.
The arm64 client here writes no MostRecent, so with two accounts in one
install nothing was picked: no added or store game reached
shortcuts.vdf ("no signed-in account yet; nothing written") and Steam
answered their launch with "App configuration unavailable".
Without MostRecent, take the account marked AutoLogin, then the one with
the latest timestamp. Both sides use the same order so the app reads the
config the guest script writes.
Steam installs Proton EasyAntiCheat Runtime (1826330) and Proton BattlEye Runtime (1161040) as tool depots. The launcher's Games tab and the desktop's game entries now skip them like the other runtimes.
The content log was read with RandomAccessFile.readLine, one byte per read, on the main thread. A 4 MB log took about 2 s on the first poll, enough for an ANR on slower devices while a download runs. It is now read through a buffered reader with the same Latin-1 lines and offset, and the per-line regexes only run on lines that can match.
Games the Epic client installed now carry their LaunchCommand arguments too, split by a new launch.split op. A doubled quote inside a quoted argument is one literal quote, and an install with a version ignores a manifest without the same build.
The manifest's arguments apply only when its build matches the installed version, only arguments the merge inserted are tracked as its own, escaped quotes split like a Windows command line, and the REDprelauncher rule needs Cyberpunk2077.exe beside it.
Feature level 12_2 makes vkd3d-proton report mesh shaders and ray tracing that Turnip doesn't provide. Caravan SandWitch (Unreal Engine 5) then builds mesh shader pipelines without a pipeline layout and the driver crashes; Cyberpunk 2077 stops at ray tracing initialization. Only these two games get 12_1, and a value set in a game's own settings still wins.
Epic manifests carry a launch command next to the launch executable, which the Epic launcher passes to the game. It was read and dropped, so Caravan SandWitch never got -PCStore=EGS and quit with "started outside of any known PC store". The manifest's launch command now joins the game's arguments, including for games already installed.
The redistributable seeder marked every process in a game's install scripts as already run, so the Ubisoft Connect installer that Ubisoft games list was skipped and they stopped with "Unable to locate Uplay PC". It is now left to run like the Vulkan runtime installer, and a marker an earlier seeding wrote is taken back out.
A user's first Steam setup never finished on an Honor Pad (Snapdragon 6
Gen 1, metered Wi-Fi with a 14 Mbit/s link). Their log stops at
`downloading Steam: bins_linuxarm64_linuxarm64 (2/17)`. The log was
shared 31 s into the session, so nothing had failed yet. The way the
download works explains why it never got further.
## Why
- **The session pauses the download.** With the default background
behavior (Manual), a session pauses as soon as DroidDeck is hidden or
the screen turns off. In the user's log that happened one second after
start, before the download had begun.
- "Keep downloads running" doesn't help. It's off by default, and it
only recognizes Steam's own game downloads, not the runtime fetching the
client.
- A first run shows nothing for minutes, so leaving the app or letting
the screen time out is exactly what people do.
- **The download is bigger than the screen says.** The client is about
360 MB in 17 parts, and part 2 alone is 110 MB. The loading screen said
"~1-2 min" and counted parts, not bytes, so on a slow link it looks
frozen at about 5%.
- **Nothing handles a bad connection.** Each part was a plain `curl
-fsSL` with no timeout, no retry and no progress output.
- A stalled connection could wait forever.
- A failed one ended the session and threw away everything fetched, so
the next try started from part 1.
## What changes
- **`droiddeck-steam-install`**:
- Parts go to `package/droiddeck-download` and stay there until the
client is installed.
- Each part resumes from the bytes on disk (`curl -C -`).
- A transfer under 4 KB/s for a minute is dropped and resumed.
- Attempts that make no progress back off 5–30 s, then give up with a
clear error. Whatever arrived is kept.
- A finished part is marked unpacked, so a later session skips it.
- Bytes that fail their checksum are fetched whole again. A server that
won't resume starts the part over. Parts left over from another client
version are dropped.
- A repair redoes every part once, and keeps what an interrupted repair
already redid.
- Every 5 s the step line also carries megabytes overall: `… (2/17), 45
of 360 MB`.
- **Loading screen** (`LoadingState`): shows `Downloading the Steam
client · 45 / 360 MB`, with the bar following the bytes. Older runtimes
still show the part name.
- **`SessionService`**: until the session's first frame, the download
watch also checks whether the client is still to be fetched (no install
stamp or client binary, or a repair requested). While it is:
- the session stays awake with its wake and Wi-Fi locks, whatever the
downloads setting says;
- the notification shows "Steam download active" with **Suspend now**;
- a `steam.client_download` event marks start and end.
- The watch is set up before the first suspend decision, so a session
started with the screen off isn't paused first. Once the client is
there, the chosen behavior applies again.
- **`droiddeck-session`**: the step no longer promises 1–2 minutes.
## Testing
- `python3 -m unittest discover -s tools/tests -p 'test_*.py'`: 497 OK,
1 skipped.
- `test_steam_install.py` gets a fake curl that drops connections,
refuses to resume or fails on cue.
- New cases cover: megabyte progress; resuming after a dropped
connection; keeping finished and partial parts across a failed session;
restarting a part the server won't resume; re-fetching bad bytes;
dropping another version's parts; a repair resumed once.
- Kotlin: `:app:testDebugUnitTest --tests
'com.droiddeck.launcher.session.*'` passes. That includes the new
`LoadingStateTest` and the `clientInstallPending` cases in
`SteamDownloadMonitorTest`.
- RedMagic NP06J, with Background behavior set to Manual and "Keep
downloads running" off (the user's settings), and a client repair
requested:
- Started Steam and sent DroidDeck to the background straight away. The
client download stayed active for 30 s with no pause, and all 360 MB
arrived. Then the session paused as Manual should.
- Stopped a second repair during part 2. Part 1 was marked unpacked, the
110 MB part 2 was kept, and the repair marker stayed.
- Started again: it resumed at 110 of 360 MB, unpacked part 2 without
fetching it again, finished, and Steam started normally.
- The loading screen showed `Downloading the Steam client · 248 / 360
MB`.
Retested 2026-10-23 on the REDMAGIC tablet after merging main, which
brought in the stable-channel fallback. Background behavior was set to
Manual with "Keep downloads running" off. A client repair was forced,
then Home was pressed right after Start:
| Build | Session while hidden | Client download |
|---|---|---|
| main | suspended 1 s after Home | stuck at part 3/17 after 90 s |
| this PR | kept awake until the client was in | all 17 parts, 362 MB in
46 s with MB progress; suspended once done |
`tools.tests.test_steam_install` (22), `SteamDownloadMonitorTest` and
`LoadingStateTest` pass.
## Not changed
- **The download is the plain zips.** Valve's manifest also lists `.vz`
(LZMA) versions, 228 MB instead of 360 MB. Using them needs a VZ decoder
in the guest; that could be a follow-up.
- **The stall and retry path** is covered by the tests but wasn't forced
on the device.
A game started from another launcher (ES-DE, a `droiddeck://game/` link,
a pinned shortcut) should send the player back to that launcher when it
closes. Instead DroidDeck always opened the Android home screen, which
is a different place whenever that launcher isn't the default Home app.
## Cause
The link is handled by `FrontEndActivity`, which starts the session in
its own task and stays behind it in a second DroidDeck task. When the
session ended, DroidDeck started `CATEGORY_HOME` to cover that leftover
task, so the player always landed on the system Home.
## Fix
When a link-launched session ends, `SessionActivity` removes the task
that handled the link (a `FrontEndActivity` task whose base intent is a
game link), then removes its own task. Android then shows whatever was
underneath: ES-DE, any other app that launched the game, or Home when
Home launched it.
Sessions started from DroidDeck's own Games tab are unchanged.
## Testing
On a REDMAGIC NP06J, with the `droiddeck://game/945360` link started
using ES-DE's flags (`NEW_TASK | CLEAR_TASK`). Among Us was closed and
"Return to launcher after a game" was on.
| Launched from | main | this branch |
|---|---|---|
| Calculator (stands in for a non-Home frontend) | Android home |
Calculator |
| Home | Android home | Android home |
| DroidDeck's Games tab | DroidDeck | DroidDeck |
## Credits
Reported on Discord by whatsupbros.
## Summary
Since 0.5.0, Snapdragon 8 Gen 2 devices (Retroid Pocket 6, Galaxy S23,
AYN Thor) pin the Steam client, its UI helper and gamescope to the
single prime core whenever the core settings were never touched. The
Steam menu and overlay lag on those devices, and games launched from
Steam get CPU-starved.
This drops that default, so 8 Gen 2 behaves like 0.4.0 and every other
chip: Android schedules Steam on all cores. Core choices users saved in
Performance are kept, and the all-core restore for games launched from a
pinned Steam client stays.
## Evidence
The device reports attached to both bug reports show the default in
effect:
| Device | SoC | Client core override | Client cores |
|---|---|---|---|
| Retroid Pocket 6 | QCS8550 | true (never set) | 7 |
| Galaxy S23 | SM8550 | true (never set) | 7 |
Both reporters say 0.4.0, which left Steam on all 8 cores, was smooth.
The PR that added the default did not benchmark it.
Tablet bench (REDMAGIC, SM8850): the 8 Gen 2 default was reproduced by
pinning Steam to one prime core (7) and games to cores 0-3. The test
scrolls the Big Picture library with the d-pad for 20 s and records
frame pacing. Two runs each:
| Steam client | UI fps | 1% low | p99 frame | off-cadence |
|---|---|---|---|---|
| all cores (this PR) | 114.4 / 115.2 | **62.5 / 62.5** | 16 ms | 4.7 /
4.0 % |
| one core (old default) | 109.7 / 110.9 | 41.7 / 41.7 | 24 ms | 8.0 /
7.0 % |
Among Us ran the same either way (58.9 vs 59.0 fps).
## Tests
`CoreAllocationPrefsTest`: absent preferences leave every core to the
scheduler, and saved choices are kept.
## Credits
- shanume (RP6) and Kacper (S23): reports, and the 8-cores-vs-1 finding.
- Kurt Himebauch (#654): the core allocation work this keeps, apart from
the default.
After a Proton update, stored DXVK / VKD3D / FEX packages can't be
deleted. Components says they're "in use" by a Proton that's gone
(`DroidDeck-Proton_11-9.1` after updating to 11-11.1) or by a bare
number (`4005446849`).
## Cause
`deletePackage` and the Storage page's in-use check count every recorded
selection:
- **Removed Protons:** swaps on Protons that are no longer installed.
- **Out-of-date swaps:** swaps from before the Proton's version changed.
The launcher already ignores these.
- **Game profiles:** a per-game pick blocked the delete with the "swap
that Proton" message, naming the game's id.
## Fix
- **Out-of-date selections:** a selection only counts while its Proton
is installed and at the version the swap was made on. That's the rule
launches already use. Deleting a package removes its stale entries.
- **Profile picks:** deleting a package also clears it from any game's
component profile, so those games go back to the Proton's own copy.
Storage cleanup still keeps profile picks.
- **Live swaps:** a package swapped into an installed Proton is still
blocked. The message now shows the Proton's name rather than its id.
No new strings.
## Testing
On a REDMAGIC NP06J, with the state from the reports recreated:
| Case | main | this branch |
|---|---|---|
| DXVK swapped into a removed `DroidDeck-Proton_11-9.1` | Deleting
failed: in use in DroidDeck-Proton_11-9.1 | Deleted, stale entry removed
|
| FEX picked in game profile `4005446849` | Deleting failed: in use in
4005446849 | Deleted, profile cleared |
| DXVK swapped into installed Proton Experimental | blocked | blocked:
in use in Proton Experimental (ARM64) |
<img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/main-stale-proton.jpg"
width="420"> <img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/main-game-profile.jpg"
width="420">
<img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/fix-game-profile.jpg"
width="420"> <img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/fix-live-blocked.jpg"
width="420">
`ComponentsSelectionTest` covers removed, out-of-date and live
selections.
## Credits
Reported on Discord by Siigonis, Bado and woodrow1712.
Fixes#1314.
Screen recorders and apps that use `AudioPlaybackCapture`
(MediaProjection) get silence from DroidDeck. The app targets SDK 28,
and Android blocks playback capture for apps below SDK 29 unless they
opt in. That was a side effect of the target SDK, not a choice.
`android:allowAudioPlaybackCapture="true"` opts in. Game audio already
plays with `AAUDIO_USAGE_GAME`, a usage that capture allows.
REDMAGIC NP06J with Among Us running, `dumpsys media.audio_policy`
("Allow playback capture log"); the game's streams are AAudio
`USAGE_GAME` with no capture-blocking flags:
| Build | |
| --- | --- |
| main | `uid=10494, allowPlaybackCapture=false,
packageName=com.droiddeck.launcher` |
| this PR | `uid=10494, allowPlaybackCapture=true,
packageName=com.droiddeck.launcher` |
## Credits
Requested and diagnosed by @WilsonNet in #1314.
Gamescope paces from the presentation timestamp. The Choreographer tick
is often still slightly ahead of the render thread, and that tick was
thrown away, so the draw time was reported instead.
A tick within one refresh of the draw is now kept, whether it is
slightly ahead or slightly behind. A missing tick, or one more than a
refresh away, still falls back to the draw time.
## Summary
Follow-up to #1412, focused on repeat work observed in completed CI
logs.
- Give APK compilation and Android tests the same build-label and
API-key inputs. Previously `generateReleaseBuildConfig` changed between
invocations and caused Kotlin/Java recompilation before tests.
- Continue executing the Android test suite on every CI run: disable
reuse of the test task's results in CI while retaining compilation
caching. Keep the existing heap and process isolation settings.
- Cache the four remote checksum-pinned model/AAR downloads used by
add-on packs. Hash-check every cache hit, atomically replace corrupt
entries only after a verified download, and verify the destination
bytes. Packs are still regenerated from current prepared components and
their checksums remain part of the APK.
- Bound download time and retry connection resets as well as HTTP
errors; keep checksum mismatches fatal.
- Include the pack recipes in runtime source freshness checks.
- Restrict PR report updates to the workflow bot's own marker comment,
addressing the minor extraction review finding from #1412.
## Evidence and validation
The previous successful run explicitly recorded changed
`BuildConfig.java` as the reason for repeated Kotlin/Java compilation
between assembly and tests. The final #1412 normal run spent 38 seconds
building/downloading packs, 5m59s building the APK, and 3m03s in the
Android test step. A forced run also hit a connection reset while
fetching pack inputs and passed on retry.
Focused tests cover cold/warm input reuse, corrupt-cache repair,
changed-pin invalidation, rejected download bytes, actual local HTTP
connection failure/retry, identical build metadata and bot-only comment
selection. Actionlint passed. The clean Linux full suite passed 1,149
tests (18 skipped) before the final Gradle test-execution policy block.
Host, APK/ABI and Android checks are green on the final head, including
the ordinary PR run against the updated main branch and both controlled
branch attempts below.
Validation will compare cold and warm runs on the same commit, confirm
cache-hit hash verification, confirm app compilation remains up-to-date
in the test invocation, and confirm the Android suite actually executes
on both runs. Source-tree, preload, APK/ABI, all-test, signing and
publishing gates remain.
Existing Gradle, CMake and Cargo caching remains. Native compilation was
tens of seconds in the inspected baseline; no speculative native cache
changes or unmeasured speed claim are included. Android test execution
itself was about 2m30s; this PR removes avoidable setup/recompilation
rather than weakening coverage or changing concurrency.
## Follow-up correction
The first CI run failed two artwork-key tests that assumed the
compiled-in API key was empty. Keeping build inputs consistent exposed
that assumption. Tests now check the configured build key and its
trimmed fallback after clearing a user key; existing pure-function
empty-key and precedence coverage remains. No key values are included in
assertion messages. The failed run confirmed BuildConfig generation and
Kotlin/Java compilation were UP-TO-DATE in the test invocation;
subsequent full runs passed. The merged #1412 main run40007656633 passed
signing, pack publishing, preview announcement and catalog updates.
## Gradle cache audit fixes
Completed-run logs showed setup-gradle was read-only on PRs.
Same-repository PRs and branch runs can now persist their own cache
entries; fork PRs remain read-only. GitHub cache ref isolation is
retained. See [Gradle cache
guidance](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md)
and [GitHub cache
scopes](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching).
PR caches consume additional shared quota; no claim of reuse between
different PR refs is made.
COMMIT_TIME now comes from SOURCE_COMMIT, matching the provenance field
and avoiding timestamp changes from temporary PR merge commits. The
source tree and pack validation remain unchanged. This enables a
meaningful same-commit warm check. Per-commit generated Java metadata
may still cause cross-commit recompilation; moving metadata into
packaged data is a separate candidate, not part of this patch. Head
9835c4e3 passed full CI and the same-commit warm rerun below.
## Confirmed final results
Both controlled runs use exactly
9835c4e3e1 and workflow_dispatch, avoiding
PR merge-tree changes between attempts.
| Measurement | First attempt | Warm attempt | Reduction |
| --- | ---: | ---: | ---: |
| Request to required gate green | 13m09s | 7m11s | 5m58s (45%) |
| Gradle APK build step | 6m11s | 2m08s | 4m03s (66%) |
| Android test step | 2m57s | 2m08s | 49s |
| Add-on pack build step | 40s | 31s | 9s |
[First
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/1)
and [warm
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/2)
both passed. Initial queue delay was 3 seconds in each. This is one
observed pair, not a guaranteed latency or isolated attribution of every
timing difference to cache changes. The first attempt already had some
dependency/build cache reuse.
Warm logs confirm the exact head's Gradle-home cache restored; release
Kotlin and Java compilation were FROM-CACHE during assembly, then
UP-TO-DATE during tests. Assembly reported 22 tasks FROM-CACHE. The
Android test executor actually ran; test results were neither cached nor
skipped. All four remote pack input hits were hash-verified; packs were
regenerated. Runtime inputs and APK native exports/dependencies
verified. The ordinary [PR
workflow](https://github.com/Droid-Deck/DroidDeck/actions/runs/40013127993/attempts/2)
also passed against the updated main merge tree.
A fresh Gradle daemon and "no cached configuration" remain expected on a
new runner: configuration/task-graph persistence is distinct from
build-output caching and currently has no configured encryption key. The
measured Kotlin/Java FROM-CACHE results demonstrate output reuse despite
that message.
This proves same-commit warm reuse. New pushes can still invalidate
compilation through changed source, toolchain, generated provenance or
pack metadata, and cache eviction/ref scope can cause misses.
Cross-commit compilation reuse is not established by this pair. Branch
validation intentionally skips main-only publishing; merged #1412's main
run40007656633 separately passed signing and publication. The user
authorized merging after the final cache-recovery correction passed.
## Final cache-recovery correction
Commit c95e1147 addresses the remaining minor review finding: after a
download passes its SHA check, an unexpected directory at the exact
hash-named cache entry is removed before the verified file is promoted.
Invalid downloads leave that directory untouched. A regression test
covers failed verification, successful repair and subsequent warm reuse.
All five focused download tests passed in a clean Linux export. All
final-head PR checks passed in
[run40018498031](https://github.com/Droid-Deck/DroidDeck/actions/runs/40018498031):
host suite ran 1,164 tests (49 platform skips), APK build and native
validation passed, Android tests executed successfully, and the
source/build validation status is green. Benchmark results above remain
measurements of 9835c4e3.
The default FEX preset (Performance + TSO) is fine for Unity. But when
the Default profile is switched to a preset
that turns TSO off (Performance, Extreme, Extreme-gn, Denuvo), Unity
players break. DroidDeck already keeps full TSO
for .NET games (`coreclr.dll`) whatever the preset; this gives Unity
players the same safety net for TSO.
Tested on a Pixel 10 Pro Fold (PowerVR DXT-48-1536), shared preset
Performance (TSO off):
| Game | Build | TSO off | TSO on |
| --- | --- | --- | --- |
| Megabonk | Unity 2023.2 IL2CPP, native Linux, "Linux x86 (FEX)" |
froze on about half of launches (every thread parked on a futex) | 5 of
5 starts, played run stayed responsive |
| Crab Game | Unity IL2CPP, Windows, Proton arm64 | 9 of 10 starts; 1
access violation in `UnityPlayer.dll` (read of a garbage pointer) | 10
of 10 starts |
Measured cost of TSO on Megabonk: none (+2% fps with TSO on, within
noise).
What changes:
- `droiddeck-game-env`: `unity_memory_ordering` sets `FEX_TSOENABLED=1`
when `UnityPlayer.dll`, `UnityPlayer.so` or
`GameAssembly.so` sits next to the executable (or one folder below). A
FEX preset chosen for the game still wins
(fixes sit between the shared preset and the per-game entry, as for
.NET).
- `droiddeck-fex`: the FEX tool now applies the environment half of
these game-file fixes, so native Linux Unity builds
get it too. Launch arguments some fixes add stay Proton-only.
- Docs paragraph in `docs/development/game-environment.md`; tests in
`test_game_environment.py` and
`test_droiddeck_fex.py`.
Device check of the change itself: with the session's
`droiddeck-game-env` patched, shared TSO off and no per-game
entry, Crab Game started with `FEX_TSOENABLED=1`.
Related to #1386, but not a fix for its Risk of Rain 2 crash: that game
is Unity Mono, which with the default preset
already has TSO on; the report points at multiblock instead (as the .NET
fix turns off). Both games tested here are
IL2CPP, so this PR doesn't change multiblock. #1386's other points
(`steam_appid.txt`, `device.txt`) aren't covered.
Tests (WSL, on main `16bedf36` with the patch): `test_game_environment`
+ `test_droiddeck_fex` 119 OK. Full `tools/tests`: 1114 tests, one
failure,
`test_appimage_run…test_a_failed_program_shows_the_log_it_wrote`, which
fails the same way without the patch.
Refs #1248
## Problem
Users report that changing a game's compatibility tool in Steam's game
properties doesn't stick: it goes back to DroidDeck's default. #1248 was
closed by #1250, but its reporter still saw it after clearing the game's
DroidDeck profile.
A game's tool can be chosen in two places, DroidDeck's per-game Proton
(`games.json`, written by `ProtonDefault.requestGame`) and Steam.
`plan_mapping` treats every `games.json` entry as an override that
always wins, so the live helper (`droiddeck-steam-compat`, on
`CompatManager.NotifyStateChanged`) sets it back within a second, and
`steam-compatibility` does again at every session start. A `null` entry
("use the default", also written by a profile reset) is forced as the
default, so resetting the profile made it worse.
Reproduced with the real `plan_mapping` (game 220, Steam set to
`droiddeck-fex`):
| `games.json` entry | before |
|---|---|
| none | kept |
| `null` (profile reset / "Default") | reverted to default |
| a DroidDeck Proton | reverted to it |
## Change
Whichever side changed a game's tool last wins:
- A DroidDeck choice is applied once (as now). Once applied, a different
pick in Steam stands:
- a pick of one of DroidDeck's Protons becomes the DroidDeck choice too
(`games.json` updated, so the app shows it);
- any other pick (FEX, a Steam Linux Runtime, clearing the forced tool)
removes the game from `games.json`.
- `null` is applied once and then dropped, so the game follows the
default like one DroidDeck never managed.
- What was applied is kept in `state.json` as `games_applied`. Choices
an earlier build already applied count as applied (when `state.json` is
from an earlier build), so upgrading respects the Steam picks users
already made.
- `games.json` is rewritten only if the app didn't replace it after it
was read, so a newer app choice is never overwritten.
- An x86-only Valve Proton picked in Steam is still replaced
(`unrunnable`), unchanged.
Both paths share the logic: `register_default` (session start, reads
config.vdf; with the client stopped, an app missing from the mapping
counts as cleared) and the live helper's `reconcile`.
## Tests
- `test_steam_compat_live.py` + `test_game_environment.py`: 109 pass
locally (103 existing + 6 new). The 6 new tests fail on the old scripts.
- Steam pick after an app choice is kept live, and a new app choice
applies again
- Steam pick of a DroidDeck Proton becomes the app's choice
- "use the default" applies once, then Steam picks stand
- upgrade: choices an earlier build applied give way to Steam picks
- an app choice written during a sync is not overwritten
- session start keeps a Steam pick from config.vdf
Not device-tested.
## Side note: `test_authorship` writes into the global git hooks
`InstalledHooksTest.setUp` runs
`tools/release/install_authorship_hooks.py` in a temp repo, but the
installer reads `core.hooksPath` from the **global** config. On a
machine with a global hooks path, running the suite replaced the user's
global `commit-msg`/`prepare-commit-msg`/`pre-push` with hooks pointing
into the deleted temp repo (every later commit failed), and the saved
originals were deleted with the temp repo. The test should isolate
`HOME`/`GIT_CONFIG_GLOBAL`. Not changed in this PR.
On a Redmi K70 (Android 14), tapping Steam drops straight back to the
main screen. The session thread dies while writing the guest network
link:
```
java.lang.IllegalArgumentException: Illegal char: - at offset 8
at android.provider.Settings$Secure.getStringForUser(Settings.java:6486)
at com.droiddeck.launcher.runtime.LinuxNetworkLinkComponent.describe(LinuxNetworkLinkComponent.kt:281)
```
The guest MAC is seeded from `ANDROID_ID`. That ROM throws from the
read, which had no guard. A failed read now uses the same fallback seed
as a missing ID. Devices that can read it keep the MAC they have now.
- `SeededMacTest`: stable MAC from an ID, and a read that throws the
ROM's exception falls back instead of crashing.
- REDMAGIC NP06J: the guest MAC is the same before and after the change
(`16:4e:5b:53:e9:ef`).
## Credits
Reported by lc666 on Discord.
Fixes#1348, fixes#1372, fixes#1388.
Since 0.6.0 the app ignores the sensor and stays in one landscape side,
even with auto-rotate on. #1287 changed the default orientation from
sensor landscape to fixed landscape, so everyone who never picked an
orientation in settings got locked.
The default is sensor landscape again. A picked orientation is still
saved and used as before.
| | Before | After |
| --- | --- | --- |
| No orientation picked | `SCREEN_ORIENTATION_LANDSCAPE`, no flip |
`SCREEN_ORIENTATION_SENSOR_LANDSCAPE`, follows the sensor |
| Orientation picked in settings | that orientation | that orientation |
Tested on REDMAGIC NP06J (Android 16) with no saved orientation:
`dumpsys activity` reports the requested orientation, and the app flips
with the device.