Skip to content

ENOENT when saving control layouts on Android 16 #1728

Description

@Errnick-code

Issue Type / 问题类型

Launcher Problem / 启动器问题

Issue Description / 问题描述

ENOENT when saving control layouts on Android 16

Important note: this investigation and fix were performed by an AI coding agent

I want to be transparent that the investigation, source-code analysis, diagnosis, proposed solution, code modifications, build process, and verification described in this report were performed by an AI coding agent.

I provided the affected device, reproduced the issue, collected ADB/logcat output, and tested the resulting patched APK on the actual device.

The AI agent inspected the ZalithLauncher2 source code, traced the file-saving path, identified the likely cause, implemented the fix, rebuilt the launcher from source, installed it on the affected device, and verified that the problem was resolved.

I am reporting the AI's analysis and the resulting fix here so that the maintainers can review it and potentially implement the fix properly upstream.


Environment

  • Device: Xiaomi Redmi 9T (M2010J19SY)
  • OS: LunarOS
  • Android: 16
  • API level: 36
  • Architecture: ARM64 / arm64-v8a
  • Zalith Launcher: 2.4.11
  • Root: Magisk
  • Package: com.movtery.zalithlauncher.v2

Problem

I could not save custom control layouts.

When trying to save a layout, Zalith Launcher reported:

Failed to save control layout

/storage/emulated/0/Android/data/com.movtery.zalithlauncher.v2/files/control_layouts/IdYe2Nxr.json:
open failed: ENOENT (No such file or directory)

The strange part was that the directory actually existed.

I verified it through ADB:

/storage/emulated/0/Android/data/com.movtery.zalithlauncher.v2/files/control_layouts

The directory had:

drwxrwx--- u0_a389 everybody

I also tested writing to the same directory through root, and root could successfully create files there.

I had already granted:

MANAGE_EXTERNAL_STORAGE

through AppOps, but this did not solve the problem.

The problem also appeared with multiple Android Minecraft launchers on this device, which initially made me suspect an Android 16 storage/scoped-storage interaction.


Investigation performed by the AI agent

The AI agent searched through the ZalithLauncher2 source code and traced the control-layout save operation.

The relevant path was approximately:

ControlManager
    -> saveControl()
    -> saveToFile()
    -> File.writeText()

The central issue found was that saveToFile() directly attempted to write the file without ensuring that the parent directory existed.

The directory was normally created during launcher initialization, but the save operation itself did not guarantee that the directory was still present and usable.

This can result in:

ENOENT

when the parent path is unavailable from the application's Java filesystem view.

The AI agent also found a fallback in PathManager that manually constructed the external storage path using:

Environment.getExternalStorageDirectory()

and then appended the /Android/data/... path.

The agent considered this problematic on modern Android versions and replaced it with the application-provided external files directory.


Fix implemented by the AI agent

1. Create the parent directory before writing

In:

LayerController/.../utils/Utils.kt

the save operation was changed to ensure that the parent directory exists before calling writeText():

file.parentFile?.mkdirs()

This prevents the save operation from relying on a directory that may no longer exist.

2. Use the Android application-specific external directory

The path handling in PathManager.kt was changed from manually constructing /storage/emulated/0/Android/data/... to using:

context.getExternalFilesDir(null)

with a fallback to the application's internal filesDir.

3. Added explicit control-layout directory validation

The AI agent added an ensureControlLayoutsDir() mechanism which:

  • checks whether control_layouts exists;
  • recreates it when necessary;
  • checks that it is actually a directory;
  • checks whether it can be written to;
  • provides a fallback to internal application storage if the external location is unavailable.

4. Protected all control-layout operations

The same directory checks were added around the relevant operations in:

ControlManager.kt
ControlManageScreen.kt
EditorViewModel.kt

including:

  • creating a new layout;
  • saving a layout;
  • importing a layout;
  • unpacking the default layout.

The existing JSON format, layout IDs, loading logic, and deletion logic were not changed.


Verification

The AI agent built a modified ARM64 version of ZalithLauncher2 from source and installed it on the affected Redmi 9T.

Before the fix:

open failed: ENOENT

After the fix, I tested the launcher directly on the same device.

The following now work correctly:

  • creating a control layout;
  • editing the layout;
  • saving the layout;
  • changing the layout;
  • restarting Zalith Launcher;
  • loading the saved layout;
  • creating the JSON file inside control_layouts.

The resulting files were successfully created in:

/storage/emulated/0/Android/data/com.movtery.zalithlauncher.v2.debug/files/control_layouts/

and no ENOENT error occurred.

So the proposed fix was not only theoretical/static analysis — the AI-generated source modification was compiled and tested on the actual affected device.


Root access

The device is rooted with Magisk.

Root was used only during the investigation to compare root-level filesystem access with the access available to the launcher application.

The final fix does not use root, su, or root permissions.

The launcher works normally with the patched filesystem/path handling.

This is important because simply granting root access would only be a workaround rather than a proper fix for the launcher.


Build information

The modified launcher was built from source using:

JDK 17
Android SDK
NDK 25.2.9519653
Gradle 9.5.0
AGP 9.3.0
ARM64 / arm64-v8a

The AI agent successfully produced both debug and release builds and tested the patched launcher on the device.

The release build also avoided the 16 KB alignment warnings seen in the unoptimized debug build.


Conclusion

This appears to be an Android 16 storage/path handling issue affecting the control-layout saving code.

The AI coding agent was able to reproduce the issue from the available information, trace it to the file/path handling code, implement a fix, build the launcher, and verify the fix on the affected device.

I am submitting this report primarily so the maintainers can review the proposed source changes and determine whether a similar fix should be integrated into ZalithLauncher2 upstream.

The complete diagnosis and patch were produced by an AI coding agent; I only provided the environment, reproduction, diagnostic output, and final device testing.

Log File / 日志文件

Failed to save control layout

/storage/emulated/0/Android/data/com.movtery.zalithlauncher.v2/files/control_layouts/IdYe2Nxr.json:
open failed: ENOENT (No such file or directory)

Screenshots or Videos / 截图或视频

No response

Reproduction Steps / 复现步骤

1. Install Zalith Launcher 2.4.11 on an Android 16 device.
2. Open the launcher.
3. Open the control layout editor.
4. Create a new custom control layout or edit an existing one.
5. Try to save the layout.
6. The launcher fails to save the JSON file and reports an `ENOENT (No such file or directory)` error.
7. The `control_layouts` directory exists on the filesystem, but the launcher is still unable to complete the file write.

Android Version / Android 版本

16

Launcher Version / 启动器版本

2.4.11

Device Information / 设备信息

Device: Xiaomi Redmi 9T Model: M2010J19SY OS: LunarOS Android: 16 API level: 36 Architecture: ARM64 / arm64-v8a Root: Magisk

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions