Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions Documentation/guides/managed-activity-debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Managed-only activity startup

Avoiding `am start -D` prevents Java-debugger waiting, but does not protect an
intentional managed-debugger pause from Android's startup ANR detection.
Eligible launches therefore use transient `am set-debug-app <package>`, without
`-w` or `--persistent`. This selects Android's `DEBUG_ON` rather than `DEBUG_WAIT`.

## Entry points and debug intent

| Entry point | Protection gate |
| --- | --- |
| `Microsoft.Android.Run/Program.cs` (`dotnet run`) | `--attach-debugger`, forwarded from existing `AndroidAttachDebugger=true` for activity launches |
| `RunActivity.cs` (`-t:Run`) | `AttachDebugger && !AllowJavaDebugging` |

Debug configuration, port forwarding, and `--no-wait` alone do not select this
behavior. Instrumentation does not use it. The task's explicit Java-debugging and
non-debug branches retain their existing behavior. The MSBuild debugging targets
already default `_AndroidAllowJavaDebugging` to `False`.

The internal `ManagedActivityLaunch` helper and its resources belong to
`Microsoft.Android.Run` and are source-linked into the task. Host callbacks
provide transport, debugger setup, fallback, and logging; the helper does not
depend on the retiring `Mono.AndroidTools` or `Xamarin.AndroidTools` libraries.
It protects startup, not the debugger connection itself: callers still supply
the runtime's debugger configuration.

## Temporary opt-out

If startup protection causes a problem, disable it in the project:

```xml
<PropertyGroup>
<_AndroidEnableManagedLaunchProtection>false</_AndroidEnableManagedLaunchProtection>
</PropertyGroup>
```

This private property is blank by default. Blank or `true` retains the normal
debug-only eligibility checks; `false` bypasses the protection transaction in both
`dotnet run` and `-t:Run`. It does not disable debugger intent or port forwarding,
enable Java debugging, or add an activity-start wait. The CLI carries the opt-out
as `--no-managed-launch-protection` alongside `--attach-debugger`.
Opting out removes startup ANR protection; it is a workaround, not a different
debugger configuration.

## Transaction and safety boundaries

- Serialize the device's debug-app transaction within each launch host. Validate
package/component identity, users, process metadata, and existing debug-app
ownership before mutation.
- Arm the transient setting, launch without `-D` or `-W`, and observe both global
state consumption and the matching process's `mDebugging=true`. A visible PID
alone is too early; waiting for application code could deadlock debugger attach.
- Drain in-flight mutations before cleanup can overtake them. Mutation drain and
cleanup each have independent five-second budgets; startup uses the debugger
timeout and lock admission has a separate 30-second limit.
- Preserve the primary error while reporting cleanup failures. Task cancellation
keeps the completion/logging pump alive until cleanup finishes. CLI timeouts
return failure, not the exit code reserved for actual Ctrl+C.

Protection is limited to Android 12+, forced cold activity launches, a single-user
device, and a confirmed default package process. `set-debug-app` force-stops the
package for **all users**, so it must not broaden a user-scoped launch. Warm,
multi-user, custom-process, or otherwise ineligible launches retain an explicitly
diagnosed unprotected path. Unknown initial dump layouts can fall back before
mutation; malformed ownership, transport failures, and post-mutation state errors
do not become success-shaped fallbacks.

CLI exit/logcat tracking uses a confirmed custom process when available, otherwise
the existing package probe. Debug launches poll for initial PID readiness rather
than using `am start -W`; ordinary launches are unchanged. ADB errors are distinct
from normal PID absence, and successful `am start` warnings on stderr are not
treated as failures.

The host lock cannot coordinate unrelated adb clients. There is no atomic
compare-and-clear API, so external ownership changes, disconnection, and late
remote execution remain limitations. An observed foreign or persistent debug-app
assignment is never cleared. Do not run competing transactions on the same device.

## Regression coverage and limits

Tests cover the shared transaction's ownership, eligibility, consumed-state,
cancellation, timeout, and cleanup invariants, plus the real launch entry points'
debug-intent gates, command/exit behavior, task diagnostics, and MSBuild arguments.
Fixtures must not touch a real adb server or device. Host-side tests do not replace
live managed-debugger startup, sustained breakpoint pause, or OEM-layout validation.

| Coverage | Owning test project |
| --- | --- |
| Shared helper and portable CLI process bridge | [Microsoft.Android.Run-Tests](../../tests/Microsoft.Android.Run-Tests/Microsoft.Android.Run-Tests.csproj) |
| `RunActivity` and shipping MSBuild argument routing | [Microsoft.Android.Build.Tasks.Tests](../../src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj) |

The task-test project requires its default .NET 11 target; do not retarget it to
.NET 10. The real SIGINT assertion is Unix-only; the other CLI cases use a portable
fake-ADB executable rather than a Bash-only bridge.

## Android references

- [ActivityManagerService: transient debug-app state and application attach](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-16.0.0_r1/services/core/java/com/android/server/am/ActivityManagerService.java)
- [ActivityThread: DEBUG_WAIT versus DEBUG_ON](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-16.0.0_r1/core/java/android/app/ActivityThread.java)
- [ProcessErrorStateRecord: debugged-process ANR exemption](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-16.0.0_r1/services/core/java/com/android/server/am/ProcessErrorStateRecord.java)
2 changes: 2 additions & 0 deletions Microsoft.Android-Tests.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Android.Benchmarks/Android.Benchmarks.csproj" />
<Project Path="tests/Microsoft.Android.Run-Tests/ManagedLaunchTestAdbProxy/ManagedLaunchTestAdbProxy.csproj" />
<Project Path="tests/Microsoft.Android.Run-Tests/Microsoft.Android.Run-Tests.csproj" />
<Project Path="tests/StartupHook/StartupHook.csproj" />
</Folder>
<Folder Name="/tests/Mono.Android-Tests/" />
Expand Down
2 changes: 2 additions & 0 deletions Microsoft.Android.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
<Project Path="tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/MonoAndroidFixture/MonoAndroidFixture.csproj" />
<Project Path="tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestAttributeFixtures/TestAttributeFixtures.csproj" />
<Project Path="tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestFixtures.csproj" />
<Project Path="tests/Microsoft.Android.Run-Tests/ManagedLaunchTestAdbProxy/ManagedLaunchTestAdbProxy.csproj" />
<Project Path="tests/Microsoft.Android.Run-Tests/Microsoft.Android.Run-Tests.csproj" />
<Project Path="tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj" />
<Project Path="tests/Xamarin.Android.Tools.Aidl-Tests/Xamarin.Android.Tools.Aidl-Tests.csproj" />
<Project Path="tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ stages:
inputs:
command: build
projects: |
$(System.DefaultWorkingDirectory)/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
$(System.DefaultWorkingDirectory)/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj
$(System.DefaultWorkingDirectory)/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj
$(System.DefaultWorkingDirectory)/tools/ls-jdks/ls-jdks.csproj
Expand All @@ -90,6 +91,7 @@ stages:
inputs:
command: test
projects: |
$(System.DefaultWorkingDirectory)/tests/Microsoft.Android.Run-Tests/Microsoft.Android.Run-Tests.csproj
$(System.DefaultWorkingDirectory)/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
$(System.DefaultWorkingDirectory)/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj
arguments: -c Debug $(XatBuildProperties)
Expand Down Expand Up @@ -132,6 +134,7 @@ stages:
inputs:
command: build
projects: |
$(System.DefaultWorkingDirectory)/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
$(System.DefaultWorkingDirectory)/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj
$(System.DefaultWorkingDirectory)/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj
$(System.DefaultWorkingDirectory)/tools/ls-jdks/ls-jdks.csproj
Expand All @@ -142,6 +145,7 @@ stages:
inputs:
command: test
projects: |
$(System.DefaultWorkingDirectory)/tests/Microsoft.Android.Run-Tests/Microsoft.Android.Run-Tests.csproj
$(System.DefaultWorkingDirectory)/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
$(System.DefaultWorkingDirectory)/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj
arguments: -c Debug $(XatBuildProperties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@

<ItemGroup>
<Compile Include="..\..\..\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\Utilities\MockBuildEngine.cs" Link="MockBuildEngine.cs" />
<Compile Include="..\..\..\..\tests\Microsoft.Android.Run-Tests\ProcessTestUtilities.cs" Link="ProcessTestUtilities.cs" />
<Compile Include="..\..\..\..\tests\Microsoft.Android.Run-Tests\ManagedLaunchTestState.cs" Link="ManagedLaunchTestState.cs" />
<Compile Include="..\..\..\..\tests\Microsoft.Android.Run-Tests\ManagedLaunchTestServer.cs" Link="ManagedLaunchTestServer.cs" />
<Content Include="..\..\..\Xamarin.Android.Build.Tasks\Microsoft.Android.Sdk\targets\Microsoft.Android.Sdk.Application.targets" Link="Microsoft.Android.Sdk.Application.targets" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Microsoft.Android.Build.Tasks.csproj" />
<ProjectReference Include="..\..\..\Xamarin.Android.Build.Debugging.Tasks\Xamarin.Android.Build.Debugging.Tasks.csproj" />
<ProjectReference Include="..\..\..\Xamarin.Android.Build.Tasks\Xamarin.Android.Build.Tasks.csproj">
<Aliases>xamarinbuildtasks</Aliases>
</ProjectReference>
Expand Down
Loading
Loading