This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
AndroidTreeView is a .NET 10 + Avalonia Windows desktop tool for inspecting, testing and managing Android devices over ADB. Despite the name it is a C#/.NET solution, not a Java/Kotlin Android app. It has two shipping executables that share the same core:
- App (
src/AndroidTreeView.App) — the full Avalonia UI: device overview, per-category details, screen mirroring (scrcpy), CLI tools, settings, updates. - Mini (
src/AndroidTreeView.Mini) — a lightweight WinForms tray/monitor app that stays resident, watches for devices, and auto-launches scrcpy once a device is connected and authorized. Mini deliberately does not carry the Avalonia/Skia runtime.
Mini.Mac is an Avalonia-based Mini variant for macOS.
Requires the .NET 10 SDK. Run from the repo root.
dotnet restore AndroidTreeView.sln
dotnet build AndroidTreeView.sln
dotnet test AndroidTreeView.sln
dotnet run --project src/AndroidTreeView.App
dotnet run --project src/AndroidTreeView.MiniBuilding on non-Windows (App/Mini target Windows) needs the targeting pack flag — CI uses it everywhere:
dotnet build AndroidTreeView.sln -p:EnableWindowsTargeting=trueRun a single test project / a single test:
dotnet test tests/AndroidTreeView.Adb.Tests
dotnet test AndroidTreeView.sln --filter "FullyQualifiedName~BatteryParser"Format check (CI runs this, non-blocking — but keep it clean):
dotnet format AndroidTreeView.sln --verify-no-changesPackage/verify the release ZIP link locally (real releases only ship via the GitHub Publish workflow,
x64 only):
./packaging/build-update-zip.ps1 -Product App -Rid win-x64
./packaging/build-update-zip.ps1 -Product Mini -Rid win-x64There are ready-made Rider/VS run configs under .run/.
The layering is strict and enforced by project references — respect the dependency direction. Lower layers never reference upper ones.
Models domain records/enums, no project deps
↑
Core interfaces, options (AppSettings, AdbOptions), typed exceptions, DeviceMonitor
↑
Adb / Infrastructure Adb: ProcessRunner, command builders, parsers, AdbDeviceService, LogcatService
↑ Infrastructure: SettingsService (JSON), logging, update check/install
Shared the shared composition root — AddAndroidTreeViewSharedServices() wires ADB, monitoring,
↑ scrcpy, settings, and update services identically for both App and Mini
App / Mini / Mini.Mac executables (each references Shared)
Key architectural rules (see docs/architecture.md for the full binding type/interface contract, and
docs/app-contract.md):
- Shared is the single wiring point. Both App and Mini call
AddAndroidTreeViewSharedServices(...)so their ADB/scrcpy/settings/update behavior can't drift. When adding a service that both should use, register it there (it usesTryAdd*), not per-app. - App is strict MVVM with CommunityToolkit.Mvvm (
[ObservableProperty]/[RelayCommand]) andMicrosoft.ExtensionsDI/Hosting/Logging.Program.csbuilds the host +IServiceProvider, then Avalonia resolvesMainWindowViewModel.ViewLocatormaps*.ViewModels.XxxViewModel→*.Views.XxxViewby name convention. - All ADB is out-of-process via
ProcessRunner(async stdout/stderr, kills the whole process tree on cancel/timeout). Device info comes from parsing ADB text output. - Parsers are pure and unit-tested. ADB output parsing lives in
AndroidTreeView.Adb.Parsersas stateless/static functions (GetPropParser,BatteryParser,AdbDevicesParser, etc.). Any change to parsing logic must come with a parser test — fixtures live under the Adb.Tests project. AdbLocatorresolves adb (configured path → PATH → common SDK locations);IAdbEnvironmentholds the current location as a singleton. If adb is missing the App shows a Setup page instead of crashing.DeviceMonitorowns aPeriodicTimerbackground loop and raisesDevicesChanged; VMs marshal to the UI thread themselves.
- File-scoped namespaces, 4-space indent, interfaces
I-prefixed,usingoutside the namespace, CRLF line endings, UTF-8. - Async-only for I/O:
async+CancellationTokenpropagated everywhere. No.Result/.Wait()/Task.Runfor ADB/process work. UI mutations only on the UI thread (Dispatcher.UIThread.Post). - Views use compiled bindings (
x:DataType) and bind only to members that exist on the VM. No business logic or ADB calls in views. - No hardcoded user-facing strings. Use localization for every user-visible string (see below).
- Safe by design: device inspection remains read-only. The only write exception is the dedicated
semi-automatic Root wizard, which may install the pinned Magisk APK and flash only an evidence-backed
boot/init_bootimage after backup, ADB-to-fastboot identity verification, unlock/layout checks, and two explicit confirmations. Never automate bootloader unlock, erase/format, or other partitions. Real flash is gated only by those in-app confirmations; there is no environment-variable override. - Keep files under ~400 lines; the App never crashes on normal ADB/device errors — map them to friendly
ErrorMessagestate.
Every user-facing string is localized — never hardcode display text. Default language is Simplified Chinese; English is the neutral/fallback resource.
- Contract:
ILocalizationService(AndroidTreeView.Core/Interfaces) —Get(key),Format(key, args), thethis[key]indexer,SetLanguage(AppLanguage), and aLanguageChangedevent.AppLanguagelives inAndroidTreeView.Core/Options/SettingsEnums.cs.Getreturns the key itself if a resource is missing, so a missing translation is visible, not a crash. - Implementation:
LocalizationServiceinsrc/AndroidTreeView.App/Localization, backed by two ResX files with identical key sets (keep them in sync — currently 339 keys each):src/AndroidTreeView.App/Resources/Strings.resx— English (neutral fallback)src/AndroidTreeView.App/Resources/Strings.zh-Hans.resx— Simplified Chinese (default)- Keys are dotted/namespaced:
app.title,nav.devices,common.refresh, etc.
- In ViewModels: inject
ILocalizationServiceand call_localization.Get("key")/.Format(...). - In XAML: use the
{loc:Localize Key=some.key}markup extension (LocalizeExtension+LocalizeKeyConverter). Bindings watchLocalizationService.LanguageTickso text refreshes live when the language changes (the indexer's ownINotifyPropertyChangedwas unreliable in this Avalonia version — that's why the plainLanguageTickcounter exists; keep using it for live-refresh bindings).
When adding a string: add the key to both ResX files, then reference it via Get/Format (VMs)
or {loc:Localize} (XAML). Never add a key to only one file.
xUnit across four projects (dotnet test AndroidTreeView.sln, or target one project — see Commands).
Fixtures are inline const strings inside the test classes, not external .txt files.
Adb.Tests— the bulk of the suite.Parsers/has one test class per parser/builder (BatteryParserTests,StorageParserTests,AdbDevicesParserTests,GetPropParserTests,RootStatusParserTests,NetworkParserTests, the*BuilderTests, etc.),Commands/covers argv building (AdbArgsTests),Services/covers device-action/screen-capture services. Any change to ADB output parsing must add/update the matching parser test here — this is where correctness is pinned.Core.Tests—AppSettingsclone/serialize,DeviceMonitorstart/stop (with a fakeIDeviceService),SemanticVersion, file-transfer service.Infrastructure.Tests—SettingsServicepersistence, update check (GitHubUpdateService) andUpdateInstaller, using test doubles inTestDoubles.cs.App.Tests— ViewModel logic with fake services (Fakes.cs): device-list reconcile keeps selection, RawProperties filtering, Logcat bounding, DI graph resolves (ServiceGraphTests), and a boot smoke test viaAvalonia.Headless(TestAppBuilder.cs,BootSmokeTests.cs). Root wizard tests cover device locking, both confirmation gates, retry/partial-write states, localization, and compiled bindings. No real ADB, no on-screen rendering.
Version is unified across runtime version, App/Mini assembly versions, manifests, and
packaging/build-update-zip.ps1 — keep them in sync when bumping (currently 1.0.7). Update channels:
android-tree-view-app (App) and android-tree-view-mini (Mini). GitHubUpdateService checks the latest
GitHub Release, compares semver, and selects the matching product asset; UpdateInstaller downloads,
verifies SHA-256, unpacks the x64 ZIP, and runs a local update script. Loose ZIPs without a supported
release.json manifest are rejected.
build/AndroidTreeView.Scrcpy.targets downloads and bundles scrcpy (which ships adb) into tools/scrcpy
at build/publish time on Windows. tools/verify-scrcpy-latest.ps1 checks for newer scrcpy releases.
The full App also imports build/AndroidTreeView.RootTools.targets, which pins and verifies the official
Magisk APK and payload-dumper-go for win-x64 / osx-arm64. Mini variants never import or ship Root tools.
Fastboot comes from pinned Android SDK Platform-Tools 37.0.0 and is hash-verified before packaging.