Imported from wxxsfxyzm/IslandRecorder (
AGENTS.md). Install upstream withnpx skills add wxxsfxyzm/IslandRecorder. Copyright stays with the author.
AGENTS.md
Purpose
This file defines repository-specific instructions for coding agents working on Island Recorder.
Use it to decide:
- where a change belongs,
- which architectural boundary must be preserved,
- which project-specific risks need extra care,
- what to verify before claiming work is complete.
Task-specific maintainer instructions take precedence over this file. For narrow requests, make the smallest coherent change that satisfies the request. For substantial features, behavior changes, or refactors spanning multiple areas, outline a short implementation plan before editing and keep it aligned with the actual implementation.
Read these first when relevant
README.md— product scope and user-facing behavior. Treat it as documentation, not the final source of truth for implementation details. Its Privacy section is the current repository privacy reference.settings.gradle.kts— active Gradle modules and repository setup.app/build.gradle.kts,build-plugins/, andgradle/libs.versions.toml— required before changing Gradle plugins, SDK levels, toolchains, variants, repositories, versions, or dependencies.
Do not update this file for one-off task details. Update it only for stable rules that future agents should repeatedly follow.
Current architecture
Island Recorder is a Kotlin Android screen recorder for supported Xiaomi/HyperOS devices, with Super Island-oriented controls. It is built around:
- Jetpack Compose UI with Miuix components.
- Koin dependency injection.
- DataStore-backed app settings.
- MediaProjection, MediaCodec, MediaMuxer, and AudioRecord/MediaRecorder based recording.
- SAF/MediaStore recording output.
- Navigation3 screen navigation.
- Quick Settings tile and notification controls.
- Optional privileged operations through Shizuku or root
app_processbinder hooks.
The architecture is layered. Keep domain models and contracts independent from Android framework implementation details.
Active modules
Confirm module assumptions in settings.gradle.kts. The current active modules
are:
:app— main Android app.:app-process— rootapp_processbridge and binder wrapper process.:hidden-api— hidden Android API declarations used by:app.build-plugins/— included build containing shared Gradle convention plugins and centralized SDK/JDK settings.
Do not assume any other top-level directory is an included module.
Package map
Under app/src/main/java/com/island/recorder/:
core/— low-level recording primitives such as audio capture, codecs, muxing, projection, and reflection helpers.data/— concrete persistence and repository implementations, including DataStore-backed settings.di/— Koin modules and dependency wiring.domain/— stable models, repository contracts, provider contracts, and business-facing types.framework/— Android/platform integrations: services, notifications, storage providers, permission checks, privileged providers, and Shizuku/root binder hook infrastructure.ui/— activities, Navigation3 routes/container, pages, Compose components, themes, and UI state.util/— general utilities only when they do not fit a more specific layer.
Preserve these boundaries. Do not move behavior into a convenient but wrong layer just to finish faster.
Core rules
Privacy
Island Recorder is privacy-first. Do not add tracking, telemetry, analytics, remote reporting, or network calls without explicit maintainer approval. Screen recordings and settings must remain local unless the user explicitly chooses an export/share action.
Recordings are written through RecordingStorageProvider: the default path uses
MediaStore under DCIM/screenrecorder, and a user-selected tree URI uses SAF.
Do not add broad storage/media permissions when scoped MediaStore or SAF APIs can
serve the use case.
Native API preference
Prefer existing native Android APIs, binder APIs, repository abstractions, and
platform-facing helpers. Do not introduce shell-command implementations as a
shortcut when a maintained native/binder path exists. Root availability probing
is the current narrow exception and lives in DeviceCapabilityProviderImpl.
Privileged safety
Privileged operations are sensitive. Keep permission checks, capability checks, fallback behavior, and failure logging explicit.
Use existing privileged abstractions:
DeviceCapabilityProviderdetects Root/Shizuku capability.PrivilegedOperationProviderchooses the active authorizer and exposes privileged use cases.DirectPrivilegedExecutordispatches into Shizuku hook or rootapp_processhook runtimes.DefaultPrivilegedServicecontains privileged operation implementations.ShizukuHookRecyclerandProcessHookRecyclermanage binder hook process lifetimes.
The project currently uses binder hook paths for privileged work. Do not reintroduce Shizuku UserService/AIDL plumbing unless explicitly requested.
User-selected authorizer
Respect the user's selected authorizer. If the user chooses Shizuku, features that require Shizuku should be disabled when Shizuku is not authorized/running, even if root is available. If the user chooses Root, root-only UI should be disabled when root is unavailable, even if Shizuku is available.
Operational fallback can exist only where the code clearly intends it and the UI state still explains the active capability accurately.
Settings and state
Settings are DataStore-backed through AppSettingsRepository.
AppPreferencesis the aggregate app settings model.RecordingSettingsis the recording-specific settings model passed to the recorder service.AppSettingsRepository.currentPreferencesis the in-process latest settings snapshot for synchronous decisions.preferencesFlowis the collected settings stream for UI and state holders.
When writing settings, keep the DataStore write and in-process snapshot update consistent. Avoid duplicate caches for individual settings unless there is a specific reason and a clear invalidation/update path.
For capability state, DeviceCapabilityProvider.refreshPrivilegeStatus() should
be called when UI needs a fresh Root/Shizuku status, especially in transient
entry points such as quick-tile launched dialogs.
Recording flow
The main recording lifecycle lives in RecorderService.
Important state rules:
RecordingState.Idlemeans the service is ready for a new recording.RecordingState.Processingmeans startup work is in progress. Treat it as busy.RecordingState.RecordingandRecordingState.Pausedmean recording is active.RecordingState.Stoppingmeans recording has been stopped but cleanup is still running. Treat it as busy. Do not start a new recording while cleanup is active.
Keep state transitions and Quick Settings tile refreshes in sync. If a change
affects start/stop/pause/resume behavior, review notification actions,
QuickTileService, RecordingShortcutActivity, and lock/screen-off handling.
Heavy recording setup and cleanup must stay off the main thread. Foreground service and MediaProjection requirements must still be satisfied on the correct thread/API path.
Recording output descriptors must be closed during cleanup. If a failed start or failed muxing path creates a partial output, use the storage provider's delete path rather than leaving orphan files behind.
Quick Settings tile and shortcut dialog
QuickTileService should be a thin control surface:
- Stop active recordings through
RecorderService.ACTION_STOP_RECORDING. - Launch
RecordingShortcutActivityonly when the recorder is truly idle. - Treat
RecordingState.Stoppingas busy. - Collect recording state and tile style while listening and update
qsTilepromptly.
RecordingShortcutActivity is a transient UI for starting recording from the
tile. Keep it focused:
- Audio source and touch visualization may be adjusted there.
- Broader app settings should remain in Settings unless explicitly requested.
- Refresh Root/Shizuku capability when opening the dialog if privilege-dependent controls are shown.
Floating controls
FloatingControlService owns the application overlay controls.
- Keep overlay work behind the
SYSTEM_ALERT_WINDOWpermission path. - Keep window-view ownership and cleanup explicit when the service closes.
- Do not move overlay implementation details into domain models.
- Prefer app drawable/string resources over framework placeholder icons/text when changing user-visible controls.
UI conventions
Use Compose and existing Miuix components/patterns. Prefer current local components and themes before introducing new UI abstractions.
Guidelines:
- Keep screen-level state collection in ViewModels or activity-level entry points as appropriate to the existing pattern.
- Keep domain/data logic out of composables.
- Use
MiuixNavContainer,Navigator, andRoutefor app-screen navigation; avoid introducing a parallel navigation stack. - Keep settings screens comprehensive, but keep shortcut/dialog surfaces compact.
- Use existing string resources for user-visible text.
- Avoid introducing Material-only controls where nearby UI uses Miuix components.
Dependency injection
Use Koin. Wiring belongs in:
di/CoreModule.ktdi/SettingsModule.ktdi/ViewModelModule.kt
Avoid ad-hoc global singletons. If a dependency needs process lifetime, register it explicitly in a Koin module and make ownership/lifecycle clear.
Gradle and dependencies
Use the Gradle wrapper:
./gradlew ...
The project requires JDK 25. SDK levels, Java/Kotlin toolchains, and common
Android settings are centralized in build-plugins; do not downgrade or loosen
them without an explicit request.
Dependency/version rules:
- Prefer
gradle/libs.versions.tomlfor dependency and plugin versions. - Follow existing version catalog naming style.
- Respect centralized repositories in
settings.gradle.kts. - Do not add project repositories in module build files.
The app uses the level flavor dimension:
Unstable— default debug/dev flavor with git-hash version suffix.Stable— release-style version name.
Credentials and signing
GitHub Packages credentials for Miuix snapshots must stay outside the repository, typically in global Gradle properties:
gpr.user=YOUR_GITHUB_USERNAME
gpr.key=YOUR_PERSONAL_ACCESS_TOKEN
The token needs read:packages. CI may use GITHUB_ACTOR and GITHUB_TOKEN.
Never commit credentials, signing keys, keystores, or inline secret values.
Release signing may use local keystore.properties or environment variables.
Keep both out of source control.
Verification
Default smoke build:
./gradlew :app:assembleUnstableDebug
For changes affecting privileged logic, hidden APIs, app_process, or module
boundaries, run:
./gradlew assembleDebug
For narrow UI/resource-only changes, :app:assembleUnstableDebug is usually
enough unless the change touches shared build logic or privileged code.
Always report:
- which commands were run,
- whether they passed,
- if verification was skipped or could not be completed, why.
Recommended workflow
- State the concrete behavior or boundary being changed.
- Locate the smallest relevant area of the repository.
- Read nearby code and follow the existing pattern.
- Update all affected layers deliberately.
- Run the narrowest meaningful verification.
- Summarize the result and any remaining risk.
Be careful with dirty worktrees. Do not revert unrelated user changes. If a file already has user edits, preserve them and work with them.
Commit messages
Use Conventional Commits when asked to commit:
fix:— bug fixes or behavior corrections.feat:— user-visible features.refactor:— code restructuring without behavior changes.docs:— documentation updates.i18n:— translation/resource text updates.build:— Gradle, dependency, CI, or toolchain changes.
Example:
fix: refresh tile state after recording cleanup