diff --git a/README.md b/README.md index f2b66b8..634aa35 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,184 @@ # WinDroid Runtime -![Status](https://img.shields.io/badge/status-planning-blue) +![Status](https://img.shields.io/badge/status-early%20development-blue) ![Platform](https://img.shields.io/badge/platform-Windows%2011-blue) ![Language](https://img.shields.io/badge/language-C%23-purple) ![License](https://img.shields.io/badge/license-Apache%202.0-green) +[Website](https://novasystemslab.org) · +[Organization](https://github.com/Nova-Systems-Lab) · +[Discussions](https://github.com/Nova-Systems-Lab/WinDroid-Runtime/discussions) · +[Issues](https://github.com/Nova-Systems-Lab/WinDroid-Runtime/issues) + **WinDroid Runtime** is an independent Android-compatible runtime and toolkit for Windows. -The long-term goal of this project is to build a modern platform that allows Android applications to run, integrate, and feel natural on Windows. The project is being designed from scratch, starting with developer tooling, APK management, ADB integration, and runtime research before moving toward experimental Android runtime and desktop integration research. +The long-term goal is to build a modular platform that allows Android applications to run, integrate, and feel natural on Windows. Development begins with WinDroid Studio, ADB integration, APK management, diagnostics, and developer tooling before moving into runtime-engine research, Windows integration, consumer experiences, and later gaming-focused work. + +> [!IMPORTANT] +> **Current project status** +> +> WinDroid Runtime is in early development. The repository currently focuses on the native Windows management application and ADB-based tooling. +> +> It is **not yet a usable Android runtime**, and the Android runtime engine, consumer edition, gaming edition, anti-cheat compatibility, and most advanced features described below are long-term plans rather than current capabilities. -> **Project Status:** Early planning and research phase. -> This repository currently contains the project vision, roadmap, and architecture planning. Production-ready runtime code has not been implemented yet. -> This project is not currently usable as an Android runtime. It is a planning-stage repository for a long-term open-source systems project. +> [!NOTE] +> **Roadmap sequencing** +> +> The current roadmap remains developer-first through Issue #36. After Issue #36 is complete, the project will open a dedicated architecture/RFC discussion to evaluate the future Developer, Desktop, and Gaming experiences. +> +> This does **not** mean those editions will be implemented immediately after Issue #36. That checkpoint begins architecture planning, feasibility work, validation, and prioritisation. Shared platform components will remain unified unless later evidence justifies a different design. --- ## Vision -Windows previously had official Android app support through Windows Subsystem for Android. After its discontinuation, there is a gap for users, developers, and students who want a clean Android-on-Windows experience without relying only on traditional emulators. +Windows previously offered official Android application support through Windows Subsystem for Android. Following its discontinuation, there is renewed interest in clean, secure, and well-integrated Android-on-Windows experiences. + +WinDroid Runtime aims to explore that space through an original, open-source Android-compatible runtime platform built independently from the ground up. + +The long-term product vision is: + +> **One shared runtime platform with specialised experiences for developers, ordinary Windows users, and gamers.** -WinDroid Runtime aims to explore that space by building an independent, open-source Android-compatible runtime and toolkit for Windows. +```text +WinDroid Platform +├── Shared Runtime Foundation +│ ├── Runtime Engine +│ ├── Android Virtual Device Layer +│ ├── Hardware Acceleration +│ ├── Graphics and Audio +│ ├── App Lifecycle Manager +│ ├── ADB and Debug Bridge +│ ├── Windows Integration Layer +│ ├── Security and Permissions +│ └── Update and Package System +│ +├── WinDroid Developer +│ └── Developers, testers, QA teams, and advanced users +│ +├── WinDroid Desktop +│ └── General users, productivity applications, and managed deployments +│ +└── WinDroid Gaming + └── Gaming-focused performance, input, and compatibility features +``` -The goal is not to reuse or modify Microsoft WSA. The goal is to build an original system from the ground up. +The project will not attempt to satisfy all three markets in its first release. + +--- + +## Product Direction + +### WinDroid Developer + +The first product direction. + +Planned areas include: + +- ADB and debugging tools +- APK installation and app management +- Device and environment information +- Logs and crash diagnostics +- Screenshots and file transfer +- Shell access +- Multiple Android environments +- Device and API-level profiles +- Snapshots +- Network and sensor simulation +- Automated testing +- IDE and CI integration +- Team and enterprise deployment tools + +The first practical releases will focus on the smaller and more achievable subset represented by the current GitHub issues. + +### WinDroid Desktop + +A future consumer and productivity experience built on the same shared runtime foundation. + +Potential features include: + +- Simple installation and onboarding +- Reliable Android application execution +- Per-app windows +- Start Menu shortcuts +- Windows notifications +- Clipboard and file sharing +- Camera, microphone, and location permission controls +- Privacy-oriented defaults +- Low idle resource usage +- Offline usability +- No advertisements or mandatory behavioural profiling +- Optional ADB access for advanced users +- Managed deployment for businesses, schools, and kiosks + +This experience will not begin until the runtime foundation reaches suitable compatibility, reliability, security, and usability gates. + +### WinDroid Gaming + +A later gaming-focused experience. + +Potential research and development areas include: + +- High and stable frame rates +- Keyboard and mouse mapping +- Controller support +- Multi-instance execution +- Macros and game profiles +- High-refresh rendering +- Low input latency +- Streaming and recording tools +- Graphics compatibility +- Performance tuning +- Game-specific optimisation +- Anti-cheat compatibility research + +> [!WARNING] +> **Anti-cheat compatibility is a long-term research goal, not a current guarantee.** +> +> WinDroid intends to pursue at least basic compatibility where technically and legally possible. Some anti-cheat systems may reject virtualised, translated, rooted, modified, or otherwise unsupported Android environments by design. Kernel-level or vendor-controlled anti-cheat systems may remain incompatible regardless of WinDroid improvements. +> +> Compatibility will be documented honestly on a per-game and per-version basis. + +Gaming work is planned after developer and desktop foundations because it requires exceptional performance, broad graphics compatibility, low latency, and extensive testing. --- ## Core Goals - Build an independent Android-compatible runtime for Windows. -- Provide a native Windows control application for managing the runtime. +- Provide a native Windows control application for managing the platform. - Support APK installation, uninstallation, launching, and debugging. - Integrate ADB-based developer tools. - Research Android image booting and runtime backends. -- Explore native-feeling Windows integration for Android apps. -- Build a safe, transparent, and open-source project structure. -- Avoid dependency on proprietary WSA binaries or branding. +- Explore native-feeling Windows integration for Android applications. +- Maintain shared runtime, ADB, security, update, and lifecycle components across future editions. +- Design for privacy, offline usability, signed updates, and explicit permission controls. +- Build a transparent and sustainable open-source project structure. +- Avoid dependency on proprietary WSA binaries or misleading branding. +- Communicate unsupported applications, services, games, and anti-cheat limitations honestly. + +--- + +## Current Development Scope + +The current repository work is focused on **WinDroid Studio and the ADB tooling foundation**. + +The present development sequence includes: + +- ADB path detection +- Safe process execution +- Device discovery and parsing +- Connected-device dashboard +- Logging and output +- APK selection and installation +- Installed package listing +- Package launching and uninstalling +- Device details +- ADB server controls +- Basic logcat viewing +- Tests, CI, and contributor infrastructure + +Completing this work demonstrates the management and developer-tooling layer. It does **not** by itself prove the final Android runtime engine. --- @@ -42,23 +186,27 @@ The goal is not to reuse or modify Microsoft WSA. The goal is to build an origin ### WinDroid Studio -The native Windows control application for managing WinDroid Runtime. +The native Windows control application for managing WinDroid Runtime and connected Android targets. -Planned features: +Current and near-term planned features: -- Runtime dashboard -- ADB detection -- Connected device/runtime detection +- Runtime and device dashboard +- ADB detection and configuration +- Connected device/runtime discovery - APK installation -- Installed app list -- App launch/uninstall controls -- Log viewer +- Installed package list +- App launch and uninstall controls +- Device details +- Log and output panel +- Logcat viewer - Runtime settings - Developer tools +WinDroid Studio is currently the developer-first interface. After Issue #36, the project will evaluate whether future user experiences should remain modes within one application or become separate front-end projects. + ### WinDroid Core -Shared project logic used across the runtime and control application. +Shared application logic and contracts. Planned responsibilities: @@ -68,21 +216,27 @@ Planned responsibilities: - Logging - Error handling - Shared models and services +- Security and permission abstractions +- Update and compatibility metadata ### WinDroid ADB Layer -A dedicated service layer for interacting with Android Debug Bridge. +A dedicated service layer for Android Debug Bridge operations. Planned features: - Detect ADB installation -- Start/stop ADB server +- Start and stop the ADB server - List connected devices - Install APK files +- List installed packages +- Launch applications - Uninstall packages -- Launch apps - Capture logs - Run shell commands +- Transfer files +- Capture screenshots +- Return structured command results ### WinDroid Engine @@ -91,136 +245,334 @@ The long-term experimental runtime backend. Research areas: - Android x86/x86_64 images -- AOSP / Generic System Images -- Virtualization on Windows -- QEMU / Hyper-V / Windows Hypervisor Platform research +- AOSP and Generic System Images +- Virtualisation on Windows +- QEMU +- Hyper-V +- Windows Hypervisor Platform - Android boot process +- Hardware acceleration - Graphics, input, audio, networking, and file bridges +- App lifecycle management +- Isolation and permissions +- Updates and rollback +- Runtime compatibility measurement + +--- + +## Architecture Principles + +1. One shared runtime foundation should serve all future product experiences. +2. The first usable product should target developers and technically advanced users. +3. Consumer and gaming experiences should begin only after technical and market validation. +4. WinDroid Studio and the WinDroid runtime engine are architecturally distinct. +5. `WinDroid.Core`, `WinDroid.Adb`, and `WinDroid.Engine` should remain shared. +6. Future editions should not duplicate the runtime engine or low-level service layers. +7. Each market expansion requires its own readiness gate, security review, success metrics, and user research. +8. Proprietary Microsoft, Google, Amazon, store, service, or anti-cheat components will not be redistributed without appropriate rights. +9. Privacy, offline usability, signed updates, and explicit permission controls are foundational requirements. +10. Scope may be reduced, delayed, or redirected when feasibility or user evidence does not support the existing plan. --- ## Development Roadmap -### Phase 0 — Planning and Research +> [!CAUTION] +> Roadmap phases describe intended direction, not fixed release dates or guaranteed features. Later phases depend on feasibility, licensing, contributor capacity, security review, performance, and real user demand. + +### Phase 0 — Planning and Project Foundation - [x] Choose project name -- [x] Define project scope +- [x] Define initial project scope - [x] Add license - [x] Create initial README -- [ ] Write architecture notes - [x] Create contribution guidelines -- [x] Document legal/trademark boundaries - -### Phase 1 — Native Windows Control App - -- [x] Create WinUI 3 / .NET project structure -- [ ] Build initial dashboard UI -- [ ] Add project settings page -- [ ] Add logging system -- [ ] Add basic runtime status panel +- [x] Document legal and trademark boundaries +- [ ] Complete initial architecture documentation +- [ ] Establish compatibility and security research notes -### Phase 2 — ADB Integration +### Phase 1 — Native Windows and ADB Foundation -- [ ] Detect ADB installation -- [ ] Allow custom ADB path -- [ ] Run `adb devices` -- [ ] Parse connected devices/emulators -- [ ] Display device information in the UI -- [ ] Start and stop ADB server +- [x] Create WinUI 3 / .NET solution structure +- [ ] Complete safe ADB process execution +- [ ] Detect and configure ADB +- [ ] Run and parse device commands +- [ ] Display connected devices +- [ ] Add settings persistence +- [ ] Add logging and output +- [ ] Add tests and CI validation -### Phase 3 — APK Management +### Phase 2 — APK and Application Management -- [ ] Select APK file from Windows -- [ ] Install APK to selected target +- [ ] Select APK files +- [ ] Install APKs to selected targets - [ ] List installed packages -- [ ] Launch installed apps -- [ ] Uninstall apps -- [ ] Clear app data -- [ ] Show install/log output clearly +- [ ] Launch installed applications +- [ ] Uninstall applications with confirmation +- [ ] Display command results and failures clearly -### Phase 4 — Developer Tools +### Phase 3 — Initial Developer Tools -- [ ] Add logcat viewer +- [ ] Add connected-device details +- [ ] Add ADB server controls +- [ ] Add basic logcat viewer - [ ] Add screenshot capture -- [ ] Add file push/pull tools +- [ ] Add file push and pull - [ ] Add basic shell command interface - [ ] Add exportable diagnostic reports -### Phase 5 — Runtime Backend Research +### Architecture Checkpoint — After Issue #36 + +After Issue #36 is complete: + +- [ ] Open a dedicated multi-experience architecture/RFC issue +- [ ] Review lessons from the current WinDroid Studio implementation +- [ ] Define what remains shared across all future editions +- [ ] Evaluate one application with modes versus separate front-end applications +- [ ] Define developer, desktop, and gaming readiness gates +- [ ] Define measurable performance and compatibility targets +- [ ] Review licensing, security, privacy, telemetry, and update requirements +- [ ] Conduct targeted user research +- [ ] Decide whether and when additional front-end projects should be created + +This checkpoint starts planning only. It does not begin immediate consumer or gaming implementation. + +### Phase 4 — Runtime Feasibility and Architecture Research -- [ ] Research AOSP x86/x86_64 boot options -- [ ] Research QEMU and Windows Hypervisor Platform +- [ ] Select and evaluate possible Android bases +- [ ] Compare virtual machine, container, compatibility-layer, and hybrid approaches +- [ ] Research supported Windows virtualisation technologies +- [ ] Define graphics, audio, input, storage, and networking strategies +- [ ] Define supported Windows and hardware baselines +- [ ] Complete component-level licensing research +- [ ] Create a security and threat model +- [ ] Define update, rollback, and recovery architecture - [ ] Boot a minimal Android-compatible image -- [ ] Connect to the runtime through ADB -- [ ] Install and launch a test APK inside the runtime +- [ ] Connect to the experimental runtime through ADB +- [ ] Install and launch a test APK -### Phase 6 — Windows Integration Research +### Phase 5 — Developer Runtime Prototype -- [ ] Explore app window forwarding -- [ ] Research input bridge -- [ ] Research clipboard sharing -- [ ] Research file sharing -- [ ] Research notification bridge -- [ ] Research Start Menu and shortcut integration +- [ ] Create repeatable Android environments +- [ ] Add environment and device profiles +- [ ] Add snapshots and recovery +- [ ] Add advanced diagnostics +- [ ] Add network and sensor simulation research +- [ ] Add automated testing hooks +- [ ] Research IDE and CI integration +- [ ] Run external developer testing +- [ ] Measure startup, stability, compatibility, and resource usage -### Phase 7 — Long-Term Runtime Goals +### Phase 6 — Windows Desktop Integration -- [ ] Independent runtime backend +- [ ] Explore per-app window forwarding +- [ ] Research Start Menu and shortcut integration +- [ ] Research notification bridging +- [ ] Research clipboard sharing +- [ ] Research file sharing +- [ ] Add host permission controls +- [ ] Define privacy-preserving defaults +- [ ] Build installer, updater, rollback, and recovery flows +- [ ] Conduct consumer and managed-deployment pilots + +### Phase 7 — Gaming Feasibility Research + +- [ ] Measure graphics performance and frame pacing +- [ ] Research low-latency input +- [ ] Add keyboard and mouse mapping prototypes +- [ ] Research controller support +- [ ] Evaluate multi-instance execution +- [ ] Research streaming and recording integration +- [ ] Build a game compatibility test programme +- [ ] Research basic anti-cheat compatibility +- [ ] Document unsupported anti-cheat systems and external restrictions +- [ ] Proceed only if technically and commercially justified + +### Phase 8 — Long-Term Platform Goals + +- [ ] Stable independent runtime backend +- [ ] Shared runtime foundation across product experiences - [ ] Per-app windows -- [ ] Shared folder controls -- [ ] Audio support -- [ ] Networking support -- [ ] GPU acceleration research +- [ ] Audio, networking, and GPU acceleration - [ ] Compatibility database -- [ ] Installer and public beta +- [ ] Modular installer +- [ ] Stable, Beta, Preview, and Canary release channels +- [ ] Developer preview +- [ ] Desktop pilot +- [ ] Gaming preview, if justified +- [ ] Public beta --- -## Proposed Repository Structure +## Future Product Structure + +No immediate repository division is planned. + +The current structure remains: ```text WinDroid-Runtime/ ├── src/ -│ ├── WinDroid.Studio/ # Native Windows control app -│ ├── WinDroid.Core/ # Shared logic and models -│ ├── WinDroid.Adb/ # ADB integration layer -│ └── WinDroid.Engine/ # Experimental runtime backend -│ -├── docs/ -│ ├── architecture.md -│ ├── roadmap.md -│ ├── research-log.md -│ └── legal-notes.md -│ -├── assets/ -│ └── branding/ -│ -├── README.md -├── LICENSE -├── CONTRIBUTING.md -├── SECURITY.md -└── .gitignore +│ ├── WinDroid.Studio/ # Current developer-first WinUI application +│ ├── WinDroid.Core/ # Shared logic, models, and abstractions +│ ├── WinDroid.Adb/ # Shared ADB integration layer +│ └── WinDroid.Engine/ # Shared experimental runtime backend ``` +After Issue #36 and the architecture/RFC review, a future structure may be considered: + +```text +WinDroid-Runtime/ +├── src/ +│ ├── WinDroid.Core/ # Shared +│ ├── WinDroid.Adb/ # Shared +│ ├── WinDroid.Engine/ # Shared +│ ├── WinDroid.Studio/ # Developer experience +│ ├── WinDroid.Desktop/ # Possible future consumer experience +│ └── WinDroid.Gaming/ # Possible future gaming experience +``` + +This structure is illustrative and has not yet been approved. + +--- + +## Readiness Gates + +### Developer Readiness + +Before broader expansion: + +- Reliable device discovery +- APK installation and uninstallation +- Stable logcat and diagnostics +- Repeatable environment creation +- Crash recovery +- Automated testing +- Measured startup and command reliability +- External technical testers +- Evidence of a specific workflow improvement + +### Desktop Readiness + +Before a consumer-facing release: + +- Simple installation and onboarding +- Stable app lifecycle handling +- Permission controls +- Signed updates and rollback +- No-data-loss recovery +- Acceptable idle resource usage +- Security review +- Compatibility testing +- Clear unsupported-app messaging + +### Gaming Readiness + +Before a gaming-focused release: + +- Proven graphics performance +- Stable frame pacing +- Low-latency input +- Keyboard, mouse, and controller testing +- Multi-instance resource controls +- Game-specific compatibility testing +- A documented anti-cheat compatibility position + +--- + +## Compatibility Programme + +Planned compatibility states: + +```text +Verified +Works with limitations +Launches but unstable +Unsupported +Blocked by external dependency +``` + +Compatibility records may include: + +- WinDroid version +- Android version +- Application or game version +- CPU architecture +- Graphics backend +- Required proprietary services +- Input method +- Known issues +- Test source +- Anti-cheat status where applicable + +Compatibility claims will be version-specific and evidence-based. + +--- + +## Security, Privacy, and Trust + +Planned security principles include: + +- Per-app and per-environment isolation +- Host filesystem boundaries +- Explicit clipboard, file, camera, microphone, and location controls +- ADB disabled by default for consumer users +- Signed update verification +- Rollback and recovery +- Clear handling of administrator privileges +- Unsafe APK warnings +- Log redaction +- Consent before uploading diagnostics +- No automatic upload of sensitive logs +- Offline operation where practical +- Enterprise telemetry controls +- No mandatory advertisements or behavioural profiling + +A complete threat model will be developed during runtime feasibility work. + +--- + +## Licensing and Proprietary Components + +WinDroid Runtime is an independent project and will not redistribute proprietary components without appropriate legal rights. + +This includes, but is not limited to: + +- Microsoft WSA components +- Google Play Store +- Google Mobile Services +- Amazon Appstore components +- Proprietary Android system images +- Proprietary drivers or firmware +- Third-party anti-cheat components +- Store or service credentials + +The project may support APK sideloading and open-source application distribution methods where legally permitted. + +Official Google Play Store or Google Mobile Services integration is not included and will not be bundled unless all applicable permission, licensing, and certification requirements are satisfied. + +--- + ## Current Solution Structure -The initial multi-project solution has been scaffolded. This is foundational -structure only — it establishes the planned architecture and builds cleanly, but -no ADB functionality, Android runtime, or virtualization backend exists yet. +The initial multi-project solution has been scaffolded. ```text WinDroid-Runtime/ ├── src/ -│ ├── WinDroid.Studio/ # WinUI 3 desktop app (unpackaged), minimal window -│ ├── WinDroid.Core/ # Class library (empty foundation) -│ ├── WinDroid.Adb/ # Class library (empty foundation, references Core) -│ └── WinDroid.Engine/ # Class library (empty architectural boundary) +│ ├── WinDroid.Studio/ # WinUI 3 desktop application +│ ├── WinDroid.Core/ # Shared class library +│ ├── WinDroid.Adb/ # ADB class library, references Core +│ └── WinDroid.Engine/ # Isolated runtime-engine boundary │ -├── tests/ # Reserved for future test projects -├── Directory.Build.props # Shared build settings -├── WinDroid.Runtime.slnx # Solution (XML format) +├── tests/ +├── docs/ +├── Directory.Build.props +├── WinDroid.Runtime.slnx ├── README.md ├── LICENSE +├── CONTRIBUTING.md +├── SECURITY.md └── .gitignore ``` @@ -233,17 +585,21 @@ WinDroid.Core -> (no project references) WinDroid.Engine -> (isolated) ``` -### Building +--- + +## Building -Prerequisites (verified with the toolchain used to scaffold this solution): +Prerequisites: - Windows 11 -- .NET SDK 10.0.x (the class libraries target `net8.0`; the app targets - `net8.0-windows10.0.19041.0`) -- Windows App SDK 2.2.0 (restored automatically via NuGet) -- Visual Studio 2026 (or 2022) with the **Windows App SDK C#** / - **.NET Desktop Development** workload and a Windows 10/11 SDK. This workload - may be required to open, build, and run `WinDroid.Studio` (WinUI 3). +- .NET SDK 10.0.x +- Windows App SDK 2.2.0, restored through NuGet +- Visual Studio 2026 or 2022 with: + - Windows App SDK C# + - .NET Desktop Development + - A Windows 10 or Windows 11 SDK + +The class libraries currently target `net8.0`. The application targets `net8.0-windows10.0.19041.0`. Build from the repository root: @@ -255,9 +611,11 @@ dotnet build .\WinDroid.Runtime.slnx --configuration Release --no-restore The solution can also be opened and built directly in Visual Studio. +--- + ## Technology Stack -The planned first-stage stack: +Current stack: - **Language:** C# - **Framework:** .NET @@ -275,16 +633,23 @@ Long-term research may involve: - Hyper-V - Windows Hypervisor Platform - VirtIO -- Graphics/input/audio bridge systems +- Graphics translation +- Input and audio bridges +- Virtualisation and container technologies + +No specific long-term backend is considered final until feasibility research is complete. + +--- ## What This Project Is WinDroid Runtime is: - An independent open-source project -- A Windows-focused Android compatibility/runtime research project +- A Windows-focused Android compatibility and runtime research project - A developer toolkit for Android-on-Windows workflows -- A long-term attempt to explore native-feeling Android app support on Windows +- A long-term platform vision with specialised developer, desktop, and gaming experiences +- A project that intends to validate feasibility and user value incrementally ## What This Project Is Not @@ -292,16 +657,18 @@ WinDroid Runtime is not: - A fork of Microsoft WSA - An official or unofficial continuation of Microsoft WSA -- A Microsoft product -- A Google product -- An Amazon product -- A repackaged emulator +- A Microsoft, Google, or Amazon product - A redistribution of proprietary WSA binaries -- A project that bundles Google Play Store or Google Play Services without proper permission +- A promise that all Android applications or games will work +- A promise of universal anti-cheat compatibility +- A project that currently provides a production-ready Android runtime +- A project that bundles Google Play Store or Google Play Services without permission + +--- ## Legal and Trademark Notice -WinDroid Runtime is not affiliated with, endorsed by, sponsored by, or connected to Microsoft, Google, Amazon, or any related organization. +WinDroid Runtime is not affiliated with, endorsed by, sponsored by, or connected to Microsoft, Google, Amazon, or any related organisation. Windows, Android, Google Play, Amazon Appstore, Microsoft, Google, Amazon, and related names, logos, and trademarks are the property of their respective owners. @@ -309,31 +676,40 @@ This project does not use Microsoft WSA binaries, Microsoft branding, Google Pla For the complete project policy, see [Legal, Licensing, and Trademark Boundaries](docs/legal-notes.md). -## App Store and Google Play Notice - -The project may support APK sideloading and open-source app distribution methods in future versions. - -Official Google Play Store or Google Mobile Services integration is not included and will not be bundled unless proper legal permission, licensing, or certification requirements are satisfied. +--- ## Contributing -This project is currently in the early planning and research stage. +WinDroid Runtime is an early-stage open-source project. -Contributors, mentors, and developers interested in the following areas are welcome: +Contributors, mentors, researchers, and developers interested in the following areas are welcome: -- C# / .NET development -- WinUI 3 desktop application development +- C# and .NET +- WinUI 3 - Android Debug Bridge tooling +- Automated testing and CI - Android internals - AOSP -- Virtualization -- Hyper-V / Windows Hypervisor Platform +- Virtualisation +- Hyper-V and Windows Hypervisor Platform - QEMU - Graphics and input systems -- Open-source project architecture - Security and sandboxing +- Performance measurement +- Compatibility testing +- Technical documentation -Contribution guidelines will be added as the project structure becomes more stable. +Before starting work: + +1. Read the complete issue and its dependencies. +2. Comment that you would like to work on it. +3. Wait for a maintainer to assign it. +4. Keep the pull request focused. +5. Link the pull request to the issue. + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete contribution process. + +--- ## Community and Support @@ -347,18 +723,25 @@ For official project work: - Use GitHub Issues for confirmed bugs and actionable tasks. - Use Pull Requests for code and documentation changes. +For private security reports, follow the [security policy](SECURITY.md) or contact [security@novasystemslab.org](mailto:security@novasystemslab.org). + +--- + ## Current First Milestone -The first practical milestone is to build WinDroid Studio v0.1, a native Windows application that can: +The first practical milestone is to build the developer-tooling foundation in WinDroid Studio. + +Near-term capabilities include: -- Detect ADB -- List connected Android devices or emulators +- Detect and configure ADB +- List connected Android devices and emulators - Display device information - Install APK files -- Launch installed apps -- View basic logs +- List, launch, and uninstall installed applications +- View command output and basic logs +- Use initial diagnostic tools -This milestone will create the foundation for deeper runtime work later. +This milestone creates the management and developer-tooling foundation for deeper runtime work later. --- @@ -372,7 +755,7 @@ This milestone will create the foundation for deeper runtime work later. >

-WinDroid Runtime is developed under **Nova Systems Lab**, an independent open-source organization focused on systems software, developer tools, platform integration, and experimental runtime technologies. +WinDroid Runtime is developed under **Nova Systems Lab**, an independent open-source organisation focused on systems software, developer tools, platform integration, and experimental runtime technologies. --- @@ -380,4 +763,4 @@ WinDroid Runtime is developed under **Nova Systems Lab**, an independent open-so This project is licensed under the Apache License 2.0. -See the LICENSE file for details. +See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/src/WinDroid.Adb/Models/AdbPathResult.cs b/src/WinDroid.Adb/Models/AdbPathResult.cs new file mode 100644 index 0000000..f20cfe7 --- /dev/null +++ b/src/WinDroid.Adb/Models/AdbPathResult.cs @@ -0,0 +1,71 @@ +namespace WinDroid.Adb.Models; + +/// +/// Represents the outcome of attempting to locate an ADB executable. +/// +/// +/// A successful result carries a non-empty and a +/// . A failed result carries a +/// and a non-empty +/// . Use and +/// to construct values that honour these invariants. +/// +public sealed class AdbPathResult +{ + /// + /// Gets a value indicating whether an ADB executable was located. + /// + public bool Found { get; init; } + + /// + /// Gets the resolved absolute path to the ADB executable when + /// is ; otherwise + /// . + /// + public string? Path { get; init; } + + /// + /// Gets a concise, user-safe explanation of why resolution failed when + /// is ; otherwise + /// . + /// + public string? ErrorMessage { get; init; } + + /// + /// Creates a successful result for the given resolved executable path. + /// + /// The resolved absolute path to the ADB executable. + /// + /// is , empty, or whitespace. + /// + public static AdbPathResult Success(string path) + { + ArgumentException.ThrowIfNullOrWhiteSpace(path); + + return new AdbPathResult + { + Found = true, + Path = path, + ErrorMessage = null, + }; + } + + /// + /// Creates a failed result with the given user-safe error message. + /// + /// A concise explanation of the failure. + /// + /// is , empty, or whitespace. + /// + public static AdbPathResult Failure(string errorMessage) + { + ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage); + + return new AdbPathResult + { + Found = false, + Path = null, + ErrorMessage = errorMessage, + }; + } +} diff --git a/src/WinDroid.Adb/Models/CommandResult.cs b/src/WinDroid.Adb/Models/CommandResult.cs new file mode 100644 index 0000000..93014a0 --- /dev/null +++ b/src/WinDroid.Adb/Models/CommandResult.cs @@ -0,0 +1,205 @@ +namespace WinDroid.Adb.Models; + +/// +/// Represents the outcome of running an external process. +/// +/// +/// Exactly one high-level outcome applies to any result: +/// +/// +/// Normal completion — is , +/// and are +/// , and is the process exit +/// code (which may be non-zero). +/// +/// +/// Timeout — is and +/// is . +/// +/// +/// Caller cancellation — is . +/// This may occur before the process is started (the caller token was +/// already cancelled, so is ) +/// or after it is started ( is ). +/// +/// +/// Startup failure — is and +/// is non-empty. +/// +/// +/// Use the static factory methods to construct values that honour these +/// invariants. +/// +public sealed class CommandResult +{ + /// + /// Sentinel exit code used when the real process exit code is unavailable, + /// for example after a startup failure or after a process is terminated + /// without a reportable exit code. + /// + public const int UnknownExitCode = -1; + + /// + /// Gets the process exit code, or when it is + /// unavailable. + /// + public int ExitCode { get; init; } + + /// + /// Gets the captured standard output. Never . Holds the + /// text of a completed stream read (including output produced before a + /// terminated process exited); if the stream did not finish draining within + /// the bounded cleanup period, this is an empty string. + /// + public string StandardOutput { get; init; } = string.Empty; + + /// + /// Gets the captured standard error. Never . Holds the + /// text of a completed stream read (including output produced before a + /// terminated process exited); if the stream did not finish draining within + /// the bounded cleanup period, this is an empty string. + /// + public string StandardError { get; init; } = string.Empty; + + /// + /// Gets a value indicating whether the process was started successfully. + /// + public bool Started { get; init; } + + /// + /// Gets a value indicating whether the process was terminated because the + /// timeout elapsed. + /// + public bool TimedOut { get; init; } + + /// + /// Gets a value indicating whether the process was terminated because the + /// caller cancelled the operation. + /// + public bool Cancelled { get; init; } + + /// + /// Gets a concise, user-safe message describing why the process could not be + /// started when is ; otherwise + /// . Never contains a stack trace. + /// + public string? ErrorMessage { get; init; } + + /// + /// Creates a result for a process that ran to completion. + /// + /// The process exit code (may be non-zero). + /// The captured standard output. + /// The captured standard error. + public static CommandResult Completed(int exitCode, string standardOutput, string standardError) + { + ArgumentNullException.ThrowIfNull(standardOutput); + ArgumentNullException.ThrowIfNull(standardError); + + return new CommandResult + { + Started = true, + TimedOut = false, + Cancelled = false, + ExitCode = exitCode, + StandardOutput = standardOutput, + StandardError = standardError, + ErrorMessage = null, + }; + } + + /// + /// Creates a result for a process that was terminated because the timeout + /// elapsed. + /// + /// + /// The exit code observed after termination, or + /// when unavailable. + /// + /// Any standard output captured before termination. + /// Any standard error captured before termination. + public static CommandResult Timeout(int exitCode, string standardOutput, string standardError) + { + ArgumentNullException.ThrowIfNull(standardOutput); + ArgumentNullException.ThrowIfNull(standardError); + + return new CommandResult + { + Started = true, + TimedOut = true, + Cancelled = false, + ExitCode = exitCode, + StandardOutput = standardOutput, + StandardError = standardError, + ErrorMessage = null, + }; + } + + /// + /// Creates a result for a process that was terminated because the caller + /// cancelled the operation. + /// + /// + /// The exit code observed after termination, or + /// when unavailable. + /// + /// Any standard output captured before termination. + /// Any standard error captured before termination. + public static CommandResult Cancellation(int exitCode, string standardOutput, string standardError) + { + ArgumentNullException.ThrowIfNull(standardOutput); + ArgumentNullException.ThrowIfNull(standardError); + + return new CommandResult + { + Started = true, + TimedOut = false, + Cancelled = true, + ExitCode = exitCode, + StandardOutput = standardOutput, + StandardError = standardError, + ErrorMessage = null, + }; + } + + /// + /// Creates a result for an operation that was cancelled before the process + /// was started, because the caller's token was already cancelled. + /// + public static CommandResult CancellationBeforeStart() + { + return new CommandResult + { + Started = false, + TimedOut = false, + Cancelled = true, + ExitCode = UnknownExitCode, + StandardOutput = string.Empty, + StandardError = string.Empty, + ErrorMessage = null, + }; + } + + /// + /// Creates a result for a process that could not be started. + /// + /// A concise, user-safe explanation of the failure. + /// + /// is , empty, or whitespace. + /// + public static CommandResult StartupFailure(string errorMessage) + { + ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage); + + return new CommandResult + { + Started = false, + TimedOut = false, + Cancelled = false, + ExitCode = UnknownExitCode, + StandardOutput = string.Empty, + StandardError = string.Empty, + ErrorMessage = errorMessage, + }; + } +} diff --git a/src/WinDroid.Adb/Services/AdbDeviceService.cs b/src/WinDroid.Adb/Services/AdbDeviceService.cs new file mode 100644 index 0000000..6eb5a31 --- /dev/null +++ b/src/WinDroid.Adb/Services/AdbDeviceService.cs @@ -0,0 +1,65 @@ +using WinDroid.Adb.Models; + +namespace WinDroid.Adb.Services; + +/// +/// Runs the adb devices command by delegating to . +/// +/// +/// This wrapper only launches the command and returns the raw +/// . It does not resolve the ADB path (the caller +/// supplies an already-resolved path, typically from +/// ) and it does not parse the output. Parsing of +/// the device listing is intentionally left to a later stage. +/// +public sealed class AdbDeviceService +{ + private readonly ProcessRunner _processRunner; + + /// + /// Initializes a new instance of the class. + /// + /// The process runner used to execute ADB. + /// + /// is . + /// + public AdbDeviceService(ProcessRunner processRunner) + { + ArgumentNullException.ThrowIfNull(processRunner); + + _processRunner = processRunner; + } + + /// + /// Runs adb devices using the supplied ADB executable path and returns + /// the raw result. + /// + /// + /// Path to the ADB executable, already resolved by the caller. It is passed + /// through unchanged; this method does not revalidate or normalize it. + /// + /// + /// Optional maximum run time, forwarded to . + /// means no timeout. + /// + /// + /// Token forwarded to to cancel execution. + /// + /// + /// The unmodified produced by + /// , including its output, exit code, and timeout + /// or cancellation state. + /// + /// + /// is , empty, or whitespace. + /// + public Task GetDevicesAsync( + string adbPath, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(adbPath); + + return _processRunner.RunAsync(adbPath, ["devices"], timeout, cancellationToken); + } +} diff --git a/src/WinDroid.Adb/Services/AdbPathResolver.cs b/src/WinDroid.Adb/Services/AdbPathResolver.cs new file mode 100644 index 0000000..87cfa96 --- /dev/null +++ b/src/WinDroid.Adb/Services/AdbPathResolver.cs @@ -0,0 +1,173 @@ +using WinDroid.Adb.Models; + +namespace WinDroid.Adb.Services; + +/// +/// Locates an ADB executable by checking an optional custom path and then the +/// directories listed in the system PATH environment variable. +/// +/// +/// This type only determines whether an ADB executable exists on disk. It does +/// not launch ADB and does not verify that a located file is a genuine ADB +/// binary. The system PATH is read on every call so that environment +/// changes during the application lifetime are honoured. +/// +public sealed class AdbPathResolver +{ + // The active application target is Windows, where the executable is named + // "adb.exe". The plain "adb" name is also checked so the portable library + // behaves sensibly on non-Windows hosts. "adb.exe" is checked first so + // resolution is deterministic when both names exist in the same directory. + private static readonly string[] ExecutableNames = ["adb.exe", "adb"]; + + /// + /// Attempts to locate an ADB executable. + /// + /// + /// Optional path to an ADB executable, typically supplied from + /// AdbSettings.CustomAdbPath. , empty, or + /// whitespace values are ignored and resolution falls back to the system + /// PATH. + /// + /// + /// A successful containing the resolved path, or + /// a failed result whose explains + /// why ADB could not be found. A missing ADB is an expected outcome and is + /// reported through the result rather than by throwing. + /// + public AdbPathResult Resolve(string? customAdbPath = null) + { + string? normalizedCustomPath = NormalizeToken(customAdbPath); + bool customPathSupplied = normalizedCustomPath is not null; + + if (customPathSupplied && File.Exists(normalizedCustomPath)) + { + string? resolved = GetFullPathSafe(normalizedCustomPath!); + if (resolved is not null) + { + return AdbPathResult.Success(resolved); + } + + // The file exists but its path could not be normalized to an + // absolute path, so fall through to the system PATH search. + } + + string? pathMatch = SearchSystemPath(); + if (pathMatch is not null) + { + return AdbPathResult.Success(pathMatch); + } + + return AdbPathResult.Failure(BuildNotFoundMessage(customPathSupplied)); + } + + /// + /// Searches the directories listed in the system PATH for a known ADB + /// executable name, returning the first match as an absolute path. + /// + private static string? SearchSystemPath() + { + string? pathVariable = Environment.GetEnvironmentVariable("PATH"); + if (string.IsNullOrEmpty(pathVariable)) + { + return null; + } + + var visitedDirectories = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string rawEntry in pathVariable.Split(Path.PathSeparator)) + { + string? directory = NormalizeToken(rawEntry); + if (directory is null || !visitedDirectories.Add(directory)) + { + // Blank or duplicate entry; skip without touching the filesystem. + continue; + } + + string? match = FindExecutableInDirectory(directory); + if (match is not null) + { + return match; + } + } + + return null; + } + + /// + /// Returns the absolute path to the first known ADB executable found in the + /// given directory, or if none is present or the + /// entry cannot be inspected. + /// + private static string? FindExecutableInDirectory(string directory) + { + foreach (string executableName in ExecutableNames) + { + string candidate = Path.Combine(directory, executableName); + + // File.Exists is exception-free and returns false for malformed or + // inaccessible paths, so unusable PATH entries are skipped safely. + if (File.Exists(candidate)) + { + string? resolved = GetFullPathSafe(candidate); + if (resolved is not null) + { + return resolved; + } + + // Exists but cannot be normalized to an absolute path; skip it. + } + } + + return null; + } + + /// + /// Trims surrounding whitespace and a single pair of matching surrounding + /// quotation marks, returning when nothing usable + /// remains. + /// + private static string? NormalizeToken(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + string trimmed = value.Trim(); + + if (trimmed.Length >= 2 && trimmed[0] == '"' && trimmed[^1] == '"') + { + trimmed = trimmed[1..^1].Trim(); + } + + return string.IsNullOrEmpty(trimmed) ? null : trimmed; + } + + /// + /// Produces an absolute, normalized path, returning + /// if the path cannot be normalized. + /// + private static string? GetFullPathSafe(string path) + { + try + { + return Path.GetFullPath(path); + } + catch (Exception ex) when ( + ex is ArgumentException + or PathTooLongException + or NotSupportedException + or System.Security.SecurityException) + { + return null; + } + } + + private static string BuildNotFoundMessage(bool customPathSupplied) => + customPathSupplied + ? "ADB could not be found. The supplied custom path did not point to a " + + "valid ADB executable file, and no ADB executable was found through " + + "the system PATH." + : "ADB could not be found through the system PATH."; +} diff --git a/src/WinDroid.Adb/Services/ProcessRunner.cs b/src/WinDroid.Adb/Services/ProcessRunner.cs new file mode 100644 index 0000000..f8597a8 --- /dev/null +++ b/src/WinDroid.Adb/Services/ProcessRunner.cs @@ -0,0 +1,301 @@ +using System.ComponentModel; +using System.Diagnostics; +using WinDroid.Adb.Models; + +namespace WinDroid.Adb.Services; + +/// +/// Runs external processes asynchronously, capturing standard output, standard +/// error, and the exit code, with optional timeout and cancellation support. +/// +/// +/// This is a low-level primitive. It launches the executable directly (never a +/// shell), passes each argument individually through +/// , and does not interpret output or +/// exit codes. ADB-specific behaviour is layered on top of it elsewhere. +/// +public sealed class ProcessRunner +{ + /// + /// Maximum time to wait for a process to exit after it has been killed, so a + /// misbehaving process cannot make execution hang indefinitely. + /// + private static readonly TimeSpan TerminationWaitTimeout = TimeSpan.FromSeconds(5); + + /// + /// Maximum time to wait for redirected output to finish draining after the + /// process has exited, so a stuck stream cannot make execution hang. + /// + private static readonly TimeSpan OutputDrainTimeout = TimeSpan.FromSeconds(5); + + /// + /// Runs the given executable asynchronously and captures its output and exit + /// code. + /// + /// The path to the executable to run. + /// + /// The arguments to pass. Each entry is added individually to + /// ; no shell quoting is applied. + /// May be for no arguments. Individual entries must not + /// be . + /// + /// + /// Optional maximum run time. When it elapses the process and its child tree + /// are terminated and the result reports . + /// means no timeout. Must be greater than zero when + /// supplied. + /// + /// + /// When cancelled, the process and its child tree are terminated and a result + /// with is returned rather than throwing. + /// + /// A describing the outcome. + /// + /// is , empty, or + /// whitespace, or an entry of is + /// . + /// + /// + /// is less than or equal to zero. + /// + public async Task RunAsync( + string executablePath, + IReadOnlyList? arguments = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(executablePath); + + if (timeout is { } requestedTimeout && requestedTimeout <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException( + nameof(timeout), + timeout, + "Timeout must be greater than zero, or null for no timeout."); + } + + var startInfo = new ProcessStartInfo + { + FileName = executablePath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + + if (arguments is not null) + { + foreach (string argument in arguments) + { + if (argument is null) + { + throw new ArgumentException( + "Argument values must not be null.", nameof(arguments)); + } + + startInfo.ArgumentList.Add(argument); + } + } + + // Honour an already-cancelled caller token before spending resources on + // launching a process only to immediately terminate it. + if (cancellationToken.IsCancellationRequested) + { + return CommandResult.CancellationBeforeStart(); + } + + using var process = new Process { StartInfo = startInfo }; + + try + { + if (!process.Start()) + { + return CommandResult.StartupFailure( + $"The process '{executablePath}' could not be started."); + } + } + catch (Exception ex) when ( + ex is Win32Exception or InvalidOperationException or PlatformNotSupportedException) + { + return CommandResult.StartupFailure( + $"Failed to start process '{executablePath}'. {ex.Message}"); + } + + // Begin draining both streams immediately. Reading them concurrently + // prevents a full pipe on one stream from blocking the process (and thus + // deadlocking) while we wait on the other. No cancellation token is + // forwarded: each read completes when its stream reaches end-of-file + // (including once the process is terminated), and its captured text is + // returned. A read that does not complete within the bounded drain + // period is reported as an empty string. + Task stdoutTask = process.StandardOutput.ReadToEndAsync(CancellationToken.None); + Task stderrTask = process.StandardError.ReadToEndAsync(CancellationToken.None); + + bool timedOut = false; + bool cancelled = false; + + using (var timeoutCts = new CancellationTokenSource()) + using (var linkedCts = + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)) + { + if (timeout is { } activeTimeout) + { + timeoutCts.CancelAfter(activeTimeout); + } + + try + { + await process.WaitForExitAsync(linkedCts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (!process.HasExited) + { + // Caller cancellation takes precedence over timeout when both + // have fired, matching the documented precedence. + if (cancellationToken.IsCancellationRequested) + { + cancelled = true; + } + else + { + timedOut = true; + } + + await TerminateProcessAsync(process).ConfigureAwait(false); + } + + // If the process exited on its own just as the token fired, fall + // through and report normal completion instead of a spurious + // timeout or cancellation. + } + } + + (string standardOutput, string standardError) = + await DrainOutputAsync(stdoutTask, stderrTask).ConfigureAwait(false); + + int exitCode = TryGetExitCode(process); + + if (cancelled) + { + return CommandResult.Cancellation(exitCode, standardOutput, standardError); + } + + if (timedOut) + { + return CommandResult.Timeout(exitCode, standardOutput, standardError); + } + + return CommandResult.Completed(exitCode, standardOutput, standardError); + } + + /// + /// Kills the process and its child tree, then waits a bounded amount of time + /// for it to exit. Tolerates the race where the process has already exited. + /// + private static async Task TerminateProcessAsync(Process process) + { + try + { + process.Kill(entireProcessTree: true); + } + catch (Exception ex) when ( + ex is InvalidOperationException or Win32Exception or NotSupportedException) + { + // The process already exited, could not be accessed, or the tree + // cannot be killed on this platform. Fall through to the bounded wait. + } + + try + { + await process.WaitForExitAsync() + .WaitAsync(TerminationWaitTimeout) + .ConfigureAwait(false); + } + catch (TimeoutException) + { + // The process did not exit within the bounded wait; stop waiting so + // execution cannot hang forever. + } + } + + /// + /// Awaits the two output reads with a bounded wait, returning whatever was + /// captured. Never throws and never hangs indefinitely. + /// + private static async Task<(string StandardOutput, string StandardError)> DrainOutputAsync( + Task stdoutTask, + Task stderrTask) + { + Task bothReads = Task.WhenAll(stdoutTask, stderrTask); + + try + { + // The reads complete when each stream reaches end-of-file, which + // happens once the process exits. The bounded wait guards against a + // process that never releases a stream. + await bothReads.WaitAsync(OutputDrainTimeout).ConfigureAwait(false); + } + catch (TimeoutException) + { + // Draining exceeded the bounded wait; return whatever completed. + } + catch (Exception ex) when (ex is IOException or ObjectDisposedException) + { + // A redirected stream broke (for example, after a kill); return + // whatever completed successfully below. + } + + string standardOutput = stdoutTask.IsCompletedSuccessfully ? stdoutTask.Result : string.Empty; + string standardError = stderrTask.IsCompletedSuccessfully ? stderrTask.Result : string.Empty; + + // A read that did not finish within the bounded drain is abandoned here. + // Observe any eventual fault so it cannot surface as an unobserved task + // exception, without blocking on the unfinished read. + ObserveEventualFault(stdoutTask); + ObserveEventualFault(stderrTask); + + return (standardOutput, standardError); + } + + /// + /// Ensures a read task's eventual fault is observed. If the task has already + /// finished, any fault is observed immediately; otherwise a fault-only + /// continuation observes it later without blocking the caller. + /// + private static void ObserveEventualFault(Task task) + { + if (task.IsCompleted) + { + _ = task.Exception; + return; + } + + _ = task.ContinueWith( + static t => _ = t.Exception, + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + /// + /// Returns the process exit code, or + /// when it is not available. + /// + private static int TryGetExitCode(Process process) + { + if (!process.HasExited) + { + return CommandResult.UnknownExitCode; + } + + try + { + return process.ExitCode; + } + catch (InvalidOperationException) + { + return CommandResult.UnknownExitCode; + } + } +} diff --git a/src/WinDroid.Studio/App.xaml.cs b/src/WinDroid.Studio/App.xaml.cs index a48f469..7a1bdde 100644 --- a/src/WinDroid.Studio/App.xaml.cs +++ b/src/WinDroid.Studio/App.xaml.cs @@ -8,25 +8,30 @@ namespace WinDroid.Studio; /// public partial class App : Application { - private readonly IDisposable _sentry; + private IDisposable? _sentry; private Window? _window; public App() { - _sentry = SentrySdk.Init(options => + InitializeComponent(); + + var sentryDsn = Environment.GetEnvironmentVariable("SENTRY_DSN"); + + if (!string.IsNullOrWhiteSpace(sentryDsn)) { - options.Dsn = Environment.GetEnvironmentVariable("SENTRY_DSN"); + _sentry = SentrySdk.Init(options => + { + options.Dsn = sentryDsn; #if DEBUG - options.Debug = true; + options.Debug = true; #else - options.Debug = false; + options.Debug = false; #endif - options.AutoSessionTracking = true; - }); - - InitializeComponent(); + options.AutoSessionTracking = true; + }); + } } protected override void OnLaunched(LaunchActivatedEventArgs args) @@ -38,6 +43,11 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) private async void OnMainWindowClosed(object sender, WindowEventArgs args) { + if (_sentry is null) + { + return; + } + await SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)); _sentry.Dispose(); }