[Android] Fix DragGestureRecognizer DragStarting Command/Event fired prematurely - #35778
Conversation
… it) (dotnet#35714) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! Follow-up to @kubaflo's review on dotnet#35687. ## What In `eng/pipelines/ci-copilot.yml`, when `parameters.Platform == 'android'`, the Android AVD was being created twice: 1. **First**, via `common/provision.yml` running the `ProvisionAndroidSdkAvdCreateAvds` MSBuild target — because `skipAndroidCreateAvds: ${{ ne(parameters.Platform, 'android') }}` evaluated to `false` for Android. That target invokes `dotnet android avd create --name "Emulator_30" … --force`. 2. **Then**, the inline `Create AVD and boot Android Emulator` script ran `avdmanager create avd -n Emulator_30 -k "system-images;android-30;google_apis_playstore;x86_64" --device "Nexus 5X" --force`. Both create the same AVD name with `--force`, so the second silently overwrites the first — no error, just ~30–60s wasted on every Copilot review pipeline run for Android. The inline script is the canonical source of truth: it pins the `google_apis_playstore` image variant, the `Nexus 5X` device profile, the `disk.dataPartition.size=2048m` shrink, and ADB key pre-auth. None of those are applied by `ProvisionAndroidSdkAvdCreateAvds`. So the right fix is to skip the provision step entirely and let the inline script own AVD creation. ## Change Pinned `skipAndroidCreateAvds: true` (with an explanatory comment) at both call sites of `common/provision.yml` in `ci-copilot.yml` (the ReviewPR stage and the Deep stage). The inline `avdmanager` blocks are untouched. This is the AVD-creation portion of dotnet#35376 being reverted — the inline script that same PR added already handles AVD creation, so the provision-step AVD creation was redundant. ## Scope This change is scoped to **`ci-copilot.yml`** only — the Copilot review pipeline. It does **not** touch the required gating pipelines: - `maui-pr` - `maui-pr-devicetests` - `maui-pr-uitests` ## Follow-up Needs to be ported to `net11.0` afterward via the automated `merge/main-to-net11.0` flow. Co-authored-by: bot <bot@test> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…authors (dotnet#35751) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Problem Two issues with the daily "PR Review Queue" automation: ### 1. Workflow runs in forks and creates duplicate tracking issues `.github/workflows/pr-review-queue.yml` had no fork guard, so the daily scheduled run (`cron: "0 8 * * 1-5"`) was firing in active forks of dotnet/maui as well as in upstream. The `gh issue create --repo ${{ github.repository }}` step targets the running repo, so each fork ended up with its own `[PR Review Queue]` issue alongside the legitimate dotnet/maui one — e.g. `PureWeen#105` and `dotnet#35732` for the same day, both pinging the fork owner. ### 2. Every PR author gets @-mentioned daily The markdown table emitted each PR author handle as a raw `@username`, which GitHub parses as a real mention. Each daily run created a new issue and notified every PR author — community contributors, partner devs, bots — even though the issue is purely an internal MAUI-team triage artifact. ## Changes **`.github/workflows/pr-review-queue.yml`** — Add the same `github.repository_owner == 'dotnet'` guard already used by 8 other dotnet-owned workflows in this repo (`backport.yml`, `rebase.yml`, `locker.yml`, `dogfood-comment.yml`, `maestro-changelog.yml`, `inclusive-heat-sensor.yml`, `dotnet-format-daily.yml`, `dotnet-autoformat-pr-push.yml`). Applied to both jobs: - `generate-report` — stops the scheduled issue creation on forks. - `validate` — stops fork-internal PRs from spending CI on the dry-run. **`.github/skills/find-reviewable-pr/scripts/query-reviewable-prs.ps1`** — Wrap author handles in backticks (`` `@username` ``) in all four table-row formats. GitHub does not parse mentions inside code spans, so no notifications fire, but the table still reads naturally as a list of author handles. ## Verification - YAML parses cleanly (`python3 -c "import yaml; yaml.safe_load(...)"`). - PowerShell escape verified — `"``@$($pr.Author)``"` renders as `` `@PureWeen` ``. - Existing renderer logic for `$showMilestone` / `$showTurn` variants left intact; only the author cell changed. ## Out of scope Other automation in this repo was checked and is already fork-safe: - The gh-aw `*.lock.yml` files (`agentic-labeler`, `daily-repo-status`, `copilot-evaluate-tests`) and `agentics-maintenance.yml` are auto-generated (`DO NOT EDIT`) and have their own pre-activation role checks. - `review-trigger.yml` is implicitly fork-safe via its actor-permission check. --------- Co-authored-by: bot <bot@test> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tartup crashes (dotnet#35724) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description PowerShell 7.4.x intermittently crashes at startup on macOS with: ``` Call to 'procargs' failed with errno 5 Unhandled exception. Microsoft.PowerShell.ManagedPSEntry+StartupException at Microsoft.PowerShell.ManagedPSEntry.ThrowOnFailure(String call, Int32 code) at Microsoft.PowerShell.ManagedPSEntry.AttemptExecPwshLogin(String[] args) at Microsoft.PowerShell.ManagedPSEntry.Main(String[] args) ``` This is [PowerShell/PowerShell#20802](PowerShell/PowerShell#20802) — open and untriaged since November 2023, **0 comments, no linked PRs, no fix shipped**. The crash happens in `AttemptExecPwshLogin`, which on macOS calls `sysctl(KERN_PROCARGS2)` to inspect its own `argv[0]` and decide whether pwsh was launched as a login shell. That `sysctl` call races intermittently and returns `errno 5`, aborting startup before pwsh reads any user script. ## Repro / impact Hit on the internal `dotnet-maui` pipeline (definition 1095), build **2990586**, during release-prep for `release/11.0.1xx-preview5`: > `##[section]Starting: Provision Android SDK - Common Packages` → `Call to 'procargs' failed with errno 5` → task fails with exit code `null`. A rerun on a different agent passed, but every macOS `pwsh:` / `PowerShell@2` step in the pipeline is a flake risk going forward. Inventory: ~57 pwsh tasks across 15 pipeline YAMLs, ~45 of them on macOS-eligible jobs. ## Fix PowerShell itself ships a short-circuit for this code path. From [`src/powershell/Program.cs`](https://github.com/PowerShell/PowerShell/blob/master/src/powershell/Program.cs): ```csharp private const string LOGIN_ENV_VAR_NAME = "__PWSH_LOGIN_CHECKED"; if (Environment.GetEnvironmentVariable(LOGIN_ENV_VAR_NAME) != null) { Environment.SetEnvironmentVariable(LOGIN_ENV_VAR_NAME, null); return; // ← skips the racy sysctl(KERN_PROCARGS2) entirely } ``` Setting `__PWSH_LOGIN_CHECKED=1` makes pwsh skip the racy syscall. This PR declares it as a pipeline-level variable in `eng/pipelines/common/variables.yml` (included by every macOS-touching pipeline — `ci.yml`, `ci-official.yml`, `ci-device-tests.yml`, `ci-copilot.yml`, `ci-uitests.yml`, `device-tests.yml`, `ui-tests.yml`, `handlers.yml`, etc.). Per [AzDO docs](https://learn.microsoft.com/azure/devops/pipelines/process/variables), user-defined variables are injected as environment variables for every task — so every `pwsh:` / `PowerShell@2` step in every job inherits it. ## Safety analysis This is safe because: - **CI never wants login-shell semantics.** No `-Login` flag, no leading `-` in `argv[0]`. The skipped code's `IsLogin()` check would have returned `false` and the function would have returned without doing anything visible. We just avoid the broken syscall that precedes that decision. - **The env var is single-use per process.** `AttemptExecPwshLogin` consumes it via `SetEnvironmentVariable(LOGIN_ENV_VAR_NAME, null)`, so nested `pwsh-launches-pwsh` scenarios still work normally (each new task gets a fresh AzDO-injected value). - **Windows is unaffected.** The read is inside a `#if UNIX` block; Windows pwsh ignores the var entirely. Setting it on Windows agents is a no-op. - **Safe even under `-Login`.** If a future step explicitly adds `-Login`, the short-circuit still routes around the broken syscall — the same syscall the login-shell *detection* relies on. So there's no regression even in that (currently nonexistent) use case. ## Empirical validation Verified locally on `pwsh 7.4.5` / macOS 26.5 (same 7.4.x minor as the CI agent that crashed): | Run | Command | Result | | --- | --- | --- | | Control | `pwsh ... -Command 'Write-Output "envvar=[$Env:__PWSH_LOGIN_CHECKED]"'` | `envvar=[]` (var unset; nothing to clear) | | Test | `__PWSH_LOGIN_CHECKED=1 pwsh ... -Command 'Write-Output "envvar=[$Env:__PWSH_LOGIN_CHECKED]"'` | `envvar=[]` ← **proves the short-circuit ran** (the only line that clears the var is inside the skip-path; if it cleared, the racy syscall was never reached) | | Soak | 100 × `__PWSH_LOGIN_CHECKED=1 pwsh ... -Command 'exit 0'` | **0/100 failures**; no behavioral change | ## Backport plan - This PR: `main` (.NET 10 SR dev). Drives the fix into ongoing servicing builds. - Will cherry-pick to `net11.0` afterward (where build 2990586 crashed during preview5 prep). Diff is verified to apply cleanly to both branches. ## Related macOS-agent flakes (out of scope) The same dnceng macOS agent pool has produced other, *unrelated* flakes during this release-prep window (e.g. `IDEDownloadableMetalToolchainCoordinator: Failed to remount` and `iOS 26.2 platform not installed` on builds 2990581 and 2990384). Those are agent-image issues and need agent-side fixes — they're not addressed here. This PR fixes one specific class of macOS flake (pwsh startup race), not all of them. Co-authored-by: bot <bot@test> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35778Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35778" |
|
/review -b feature/enhanced-reviewer |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
kubaflo
left a comment
There was a problem hiding this comment.
Looks good; is this pr ready?
|
/review -b feature/enhanced-reviewer -p android |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please check the ai's suggestions?
|
/review -b feature/enhanced-reviewer |
Addressed the suggestions |
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@HarishwaranVijayakumar — new AI review results are available based on this last commit:
d19b1d9.
Update shared file To request a fresh review after new comments or commits, comment/review rerun.
Review Sessions — click to expand
Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: e904e900
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue35752 Issue35752 |
✅ FAIL — 2337s | ✅ PASS — 531s |
🔴 Without fix — 🖥️ Issue35752: FAIL ✅ · 2337s
(truncated to last 15,000 chars)
.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
Build FAILED.
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
0 Warning(s)
1 Error(s)
Time Elapsed 00:17:15.11
* daemon not running; starting now at tcp:5037
* daemon started successfully
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:08:28.62
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 2.44 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 7 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 5.64 sec).
Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 6 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 3 ms).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 13 ms).
Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 3.24 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 1.78 sec).
5 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.51] Discovered: Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/08/2026 12:15:35 FixtureSetup for Issue35752(Android)
>>>>> 06/08/2026 12:15:37 DragStartingShouldNotFireOnTapButShouldFireOnDrag Start
>>>>> 06/08/2026 12:15:41 DragStartingShouldNotFireOnTapButShouldFireOnDrag Stop
>>>>> 06/08/2026 12:15:41 Log types: logcat, bugreport, server
Failed DragStartingShouldNotFireOnTapButShouldFireOnDrag [4 s]
Error Message:
DragStarting should not fire on a quick tap
Assert.That(dragCount, Is.EqualTo("0"))
String lengths are both 1. Strings differ at index 0.
Expected: "0"
But was: "1"
-----------^
Stack Trace:
at Microsoft.Maui.TestCases.Tests.Issues.Issue35752.DragStartingShouldNotFireOnTapButShouldFireOnDrag() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35752.cs:line 26
1) at Microsoft.Maui.TestCases.Tests.Issues.Issue35752.DragStartingShouldNotFireOnTapButShouldFireOnDrag() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35752.cs:line 26
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35752.trx
Test Run Failed.
Total tests: 1
Failed: 1
Total time: 28.2989 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35752.trx
🟢 With fix — 🖥️ Issue35752: PASS ✅ · 531s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:07:03.08
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.80-ci+azdo.14309771
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.12] Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.55] Discovered: Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 06/08/2026 12:34:00 FixtureSetup for Issue35752(Android)
>>>>> 06/08/2026 12:34:02 DragStartingShouldNotFireOnTapButShouldFireOnDrag Start
>>>>> 06/08/2026 12:34:08 DragStartingShouldNotFireOnTapButShouldFireOnDrag Stop
Passed DragStartingShouldNotFireOnTapButShouldFireOnDrag [6 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35752.trx
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 19.4316 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue35752.trx
📁 Fix files reverted (3 files)
eng/pipelines/ci-copilot.ymleng/pipelines/common/variables.ymlsrc/Controls/src/Core/Platform/Android/InnerGestureListener.cs
UI Tests — DragAndDrop
Detected UI test categories: DragAndDrop
✅ Deep UI tests — 3 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
DragAndDrop |
3/3 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
Pre-Flight — Context & Validation
Issue: #35752 - [Android] DragGestureRecognizer DragStarting Command/Event fired prematurely
PR: #35778 - [Android] Fix DragGestureRecognizer DragStarting Command/Event fired prematurely
Platforms Affected: Android
Files Changed: 1 implementation, 2 test
Key Findings
- Android drag-only views did not consume
ACTION_DOWNafter the regression from PR #21547, so Android stopped delivering the touch sequence andACTION_UPnever reachedGestureDetectorto cancel the long-press timer. - The PR fix adds drag recognizer presence to
InnerGestureListener.HasAnyGestures(), causingOnDown()to returntruefor drag-only views and restoringACTION_UPdelivery. - Added UI coverage exercises both quick tap (must not fire
DragStarting) and real drag (must fire once) on Android. - Prior inline review feedback about the test category and wait duration was addressed in the latest PR files: the test uses
UITestCategories.DragAndDropand waits 600ms after tap.
Code Review Summary
Verdict: LGTM
Confidence: high
Errors: 0 | Warnings: 1 | Suggestions: 1
Key code review findings:
⚠️ src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35752.cs:23usesThread.Sleep(600)without documenting that it intentionally waits beyond Android's long-press threshold; functionally justified, but worth documenting for maintainability.- 💡 The UI test is Android-specific in behavior but has no explicit non-Android guard; likely acceptable because the issue page is marked
PlatformAffected.Android, but worth considering if the test runs broadly.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #35778 | Include drag gestures in InnerGestureListener.HasAnyGestures() so OnDown() consumes drag-only touch sequences. |
✅ PASSED (Gate) | src/Controls/src/Core/Platform/Android/InnerGestureListener.cs |
Original PR |
Code Review — Deep Analysis
Code Review — PR #35778
Independent Assessment
What this changes: InnerGestureListener.HasAnyGestures() is extended to also return true when the view has a DragGestureRecognizer, by adding || (_dragAndDropGestureHandler?.HasAnyDragGestures() ?? false) to the existing Pan/Tap/Swipe check. Two test files are added: a HostApp page demonstrating the bug and a NUnit UI test asserting the fixed behavior.
Inferred motivation: HasAnyGestures() is called from OnDown() (GestureDetector.IOnGestureListener.OnDown). When that method returns true, Android marks the ACTION_DOWN event as consumed and keeps delivering subsequent events (ACTION_MOVE, ACTION_UP) to the view. When it returns false, Android stops delivering — meaning ACTION_UP never arrives, the GestureDetector's internal long-press timer is never cancelled, and DragStarting fires spuriously after any tap. Adding drag to the check restores the expected consume-on-down behavior for drag-only views.
Reconciliation with PR Narrative
Author claims: Regression introduced by PR #21547 changed e.Handled = true to e.Handled = OnTouchEvent(e.Event), where OnTouchEvent returns InnerGestureListener.OnDown(). Since OnDown() calls HasAnyGestures() which did not include drag, drag-only views returned false, suppressing subsequent events and letting the long-press timer fire on every tap.
Agreement/disagreement: The root cause analysis is correct and maps to the code. The implementation is the minimal direct fix for the missing drag predicate.
Findings
⚠️ Warning — Thread.Sleep(600) in UI test is undocumented
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35752.cs:23 waits after a tap before asserting that DragStarting did not fire. This is functionally justified because the test is verifying absence of a delayed long-press callback, but the reason should be documented so future maintainers do not shorten or remove the wait.
💡 Suggestion — No explicit platform guard on the UI test
The bug is Android-specific and the HostApp page is marked PlatformAffected.Android. The test may still be discovered in broader runs; consider aligning the executable test scope with the Android-specific issue if non-Android drag semantics create false positives later.
Devil's Advocate
Returning true from OnDown() for drag-only views means the view consumes the touch sequence, but that matches pre-regression behavior and is required so ACTION_UP reaches GestureDetector and cancels the long-press timer. EnableLongPressGestures already depends on drag gesture presence, so the PR is consistent with the existing long-press activation path. The null-conditional pattern in HasAnyGestures() also matches the surrounding helper checks.
Verdict: LGTM
Confidence: high
Summary: The one-line implementation fix is correct, minimal, and Android-scoped. It restores delivery of ACTION_UP for drag-only views and the added UI test covers both tap and drag behavior. The only concerns are test maintainability refinements, not blockers.
Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Use EnableLongPressGestures in InnerGestureListener.OnDown() so drag-only views consume ACTION_DOWN. |
✅ PASS | 1 file | Passed Android Issue35752 UI test; close variant of PR fix. |
| 2 | try-fix | Force-consume unhandled drag ACTION_DOWN in GesturePlatformManager.Android.OnTouchEvent() and add ViewHasDragGestures(). |
✅ PASS | 1 file | Passed Android Issue35752 UI test; structurally different but broader/less direct than PR. |
| PR | PR #35778 | Add drag recognizer presence to InnerGestureListener.HasAnyGestures(). |
✅ PASSED (Gate) | 1 file | Original PR; direct missing-predicate fix. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 2 | No | Remaining possible approach (TapAndPanGestureDetector.OnTouchEvent returning true on down when long-press is enabled) is a trivial reformulation of try-fix-2, not meaningfully different. No later layer can cancel the long-press timer once ACTION_UP is lost. |
Exhausted: Yes
Selected Fix: PR's fix — It is the simplest and most semantically direct fix: HasAnyGestures() was the missing predicate used by OnDown(), and adding drag there restores the correct Android touch-stream ownership with fewer moving parts than the platform-manager candidate.
Report — Final Recommendation
Comparative Fix Report — PR #35778
Candidates evaluated
| Candidate | Approach | Regression result | Assessment |
|---|---|---|---|
pr |
Add DragGestureRecognizer presence to InnerGestureListener.HasAnyGestures(), so OnDown() consumes drag-only Android touch sequences. |
Passed | Directly fixes the missing predicate at the decision point that controls Android touch-stream ownership. |
pr-plus-reviewer |
Raw PR plus expert-reviewer feedback. | Passed | Expert reviewer returned no actionable findings, so this is identical to pr. |
try-fix-1 |
Change InnerGestureListener.OnDown() to return true when `HasAnyGestures() |
EnableLongPressGestures`. | |
try-fix-2 |
Post-process unconsumed ACTION_DOWN in GesturePlatformManager.Android.OnTouchEvent() when the view has drag gestures. |
Passed | Correct but broader and less direct; duplicates drag recognizer detection in the platform manager instead of fixing the listener predicate used by OnDown(). |
Ranking
pr/pr-plus-reviewertry-fix-1try-fix-2
No candidate failed regression tests, so ranking is based on correctness, locality, maintainability, and consistency with existing Android gesture abstractions.
Winner
pr is the winning candidate. It is tied behaviorally with pr-plus-reviewer because the expert reviewer produced no actionable changes, and it is preferable to both try-fix candidates because it repairs the missing drag predicate exactly where OnDown() decides whether the view is interested in the remainder of the Android touch sequence.
Rationale
The regression occurred because drag-only views were not represented in InnerGestureListener.HasAnyGestures(), even though the same listener enables long-press handling for drag gestures. Adding drag to that helper makes the listener's gesture-presence predicate complete and restores the pre-regression event flow with a one-line, low-risk Android-scoped change.
try-fix-1 also passes because EnableLongPressGestures currently maps to HasAnyDragGestures(), but it leaves HasAnyGestures() inaccurate and couples OnDown() to the implementation detail that drag uses long press. try-fix-2 also passes, but it adds another drag scan and fixes the symptom one layer higher after the detector has already reported OnDown() as unconsumed.
Future Action — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
kubaflo
left a comment
There was a problem hiding this comment.
Looks good! Is it ready to merge?
Yes, its ready. |
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
…prematurely (#35778) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Issue Details - On Android, a view that has only a DragGestureRecognizer incorrectly raises DragStarting on a quick tap. The drag event fires after the user lifts their finger, even though no long-press or drag motion occurred. ### Root Cause of the issue - Android's native `GestureDetector` starts an internal long-press timer (~400ms) on `ACTION_DOWN`. It relies on receiving `ACTION_UP` to cancel that timer when the user lifts their finger quickly (tap). - **Key fact:** If a view does NOT consume `ACTION_DOWN` (returns `e.Handled = false`), Android stops delivering subsequent touch events (`ACTION_MOVE`, `ACTION_UP`) to that view. ### Before Regression `e.Handled` was always `true` in `OnPlatformViewTouched`, so drag-only views always consumed `ACTION_DOWN`. - **Tap:** `ACTION_DOWN` consumed → timer starts → finger lifts → `ACTION_UP` delivered → timer cancelled → no DragStarting - **Long press:** `ACTION_DOWN` consumed → timer starts → finger stays → timer expires → `OnLongPress()` → DragStarting fires ### After Regression PR [#21547](#21547) changed to `e.Handled = OnTouchEvent(e.Event)`, which returns the result of `InnerGestureListener.OnDown()`. The `OnDown()` method checks `HasAnyGestures()` which only considers Pan/Tap/Swipe — **not Drag**. So for drag-only views, `OnDown()` returns `false` → `e.Handled = false`. - **Tap:** `ACTION_DOWN` NOT consumed → timer starts → finger lifts → `ACTION_UP` **never delivered** → timer never cancelled → timer expires → DragStarting fires - **Long press:** Same as tap — DragStarting fires regardless of touch duration ### Description of Change **Gesture Handling Improvements:** - Updated the `HasAnyGestures()` method in `InnerGestureListener` to include a check for drag gestures using the `_dragAndDropGestureHandler?.HasAnyDragGestures()` condition, ensuring drag gestures are properly distinguished from taps. **Testing and Verification:** - Added a new test page (`Issue35752`) in `TestCases.HostApp` that visually and programmatically demonstrates the issue and its resolution, allowing manual verification that `DragStarting` only fires on drag, not on tap. - Introduced an automated UI test in `TestCases.Shared.Tests` to assert that `DragStarting` does not fire on a quick tap but does fire on an actual drag, preventing regressions. <!-- Enter description of the fix in this section --> ### Regression details The regression was introduced by PR [21547](#21547) ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #35752 ### Tested the behaviour in the following platforms - [x] - Windows - [x] - Android - [x] - iOS - [x] - Mac | Before | After | |----------|----------| | <video src="https://github.com/user-attachments/assets/5ada0f41-2532-4a35-8377-44c49f4d3a71"> | <video src="https://github.com/user-attachments/assets/4a0cd419-1269-4cd6-a9a6-ad533581326b"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. --> ---------
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
Root Cause of the issue
Android's native
GestureDetectorstarts an internal long-press timer (~400ms) onACTION_DOWN. It relies on receivingACTION_UPto cancel that timer when the user lifts their finger quickly (tap).Key fact: If a view does NOT consume
ACTION_DOWN(returnse.Handled = false), Android stops delivering subsequent touch events (ACTION_MOVE,ACTION_UP) to that view.Before Regression
e.Handledwas alwaystrueinOnPlatformViewTouched, so drag-only views always consumedACTION_DOWN.ACTION_DOWNconsumed → timer starts → finger lifts →ACTION_UPdelivered → timer cancelled → no DragStartingACTION_DOWNconsumed → timer starts → finger stays → timer expires →OnLongPress()→ DragStarting firesAfter Regression
PR #21547 changed to
e.Handled = OnTouchEvent(e.Event), which returns the result ofInnerGestureListener.OnDown(). TheOnDown()method checksHasAnyGestures()which only considers Pan/Tap/Swipe — not Drag. So for drag-only views,OnDown()returnsfalse→e.Handled = false.ACTION_DOWNNOT consumed → timer starts → finger lifts →ACTION_UPnever delivered → timer never cancelled → timer expires → DragStarting firesDescription of Change
Gesture Handling Improvements:
HasAnyGestures()method inInnerGestureListenerto include a check for drag gestures using the_dragAndDropGestureHandler?.HasAnyDragGestures()condition, ensuring drag gestures are properly distinguished from taps.Testing and Verification:
Issue35752) inTestCases.HostAppthat visually and programmatically demonstrates the issue and its resolution, allowing manual verification thatDragStartingonly fires on drag, not on tap.TestCases.Shared.Teststo assert thatDragStartingdoes not fire on a quick tap but does fire on an actual drag, preventing regressions.Regression details
The regression was introduced by PR 21547
Issues Fixed
Fixes #35752
Tested the behaviour in the following platforms
Before-35752.mov
After-35752.mov