Skip to content

[WOOMOB-3851] Request the Android 17 local network permission for POS phone-as-reader - #16418

Open
kidinov wants to merge 7 commits into
issue/increment-target-sdk-37from
issue/WOOMOB-3851-local-network-permission
Open

[WOOMOB-3851] Request the Android 17 local network permission for POS phone-as-reader#16418
kidinov wants to merge 7 commits into
issue/increment-target-sdk-37from
issue/WOOMOB-3851-local-network-permission

Conversation

@kidinov

@kidinov kidinov commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes WOOMOB-3851

On Android 17, ACCESS_LOCAL_NETWORK becomes a runtime permission for apps targeting SDK 37. Nothing requested it, so Remote Tap to Pay broke on the targetSdk 37 bump: the phone's NsdManager.registerService threw SecurityException: Missing local network permission, and the tablet's TLS socket to the phone timed out. This declares the permission and asks for it on both sides.

The phone treats it as a hard requirement, gated before the session starts, next to the existing location gate. The tablet only asks when the merchant actually picks a phone reader — mDNS browsing is not gated by the permission, so putting it in the pre-discovery requirement chain would have made Bluetooth-only merchants answer a prompt they never need.

Below API 37 hasLocalNetworkPermission() returns true, so nothing changes on Android 16 and earlier.

Test Steps

On an Android 17 (API 37) device or emulator:

  1. Payments → Card Reader Mode. Confirm the "Local network permission required" screen appears, and granting takes you to Ready to pair.
  2. Deny twice, and confirm the screen switches to the blocked variant with Open Settings. Grant it in Settings, come back, and confirm the session starts without reopening the screen.
  3. On a tablet, open POS → Connect your reader → pick a phone reader. Confirm the "Local network permission required" dialog appears and connecting succeeds after granting.

On Android 16 or earlier, run the same flows and confirm no new prompt appears and pairing still works.

Images/gif

Phone, Android 17 — before vs after

Before (no permission requested) After (permission requested) After granting

Phone, Android 17 — system prompt and the blocked variant

ACCESS_LOCAL_NETWORK sits in the Nearby devices runtime group, so Android renders it with the same wording as the Bluetooth prompt. Whether a system prompt appears depends on whether the app already holds that group: with Bluetooth granted the request is auto-granted silently, and without it the prompt is shown. Reader mode never asks for Bluetooth, so the phone always prompts.

System prompt Denied twice

POS tablet, Android 17 — asked only when a phone reader is picked

Since #16406 a returning phone-reader merchant skips the Bluetooth gates, so the tablet holds no Nearby devices permission and gets the system prompt too.

In-app gate System prompt

Android 16 regression check — real phone + tablet, no new prompt

Tablet finds the phone Tablet connected Phone paired
  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

@kidinov kidinov added this to the 25.5 milestone Aug 14, 2026
@wpmobilebot

wpmobilebot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Project manifest changes for WooCommerce

The following changes in the WooCommerce's merged AndroidManifest.xml file were detected (build variant: vanillaRelease):

--- ./build/reports/diff_manifest/WooCommerce/vanillaRelease/base_manifest.txt	2026-08-14 15:29:14.057744198 +0000
+++ ./build/reports/diff_manifest/WooCommerce/vanillaRelease/head_manifest.txt	2026-08-14 15:29:16.736610451 +0000
@@ -19,6 +19,7 @@
     <uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
     <uses-permission android:name="android.permission.CAMERA" />
 
     <queries>

Go to https://buildkite.com/automattic/woocommerce-android/builds/41583/canvas?sid=01a000e1-ad01-4024-8776-2755947e1fed, click on the Artifacts tab and audit the files.

@wpmobilebot

wpmobilebot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number778
Version25.4-rc-1
Application IDcom.woocommerce.android.prealpha
Commit78c069a
Installation URL6nntpgt1rgbb0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@kidinov
kidinov requested a review from malinajirka August 14, 2026 14:53
@kidinov
kidinov marked this pull request as ready for review August 14, 2026 15:01

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - Found 1 potential issue

The permission plumbing is clean and consistent across both the store (CardReaderModeActivity/CardReaderModeViewModel) and POS (WooPosCardReaderConnectionController) paths, the onResume/recheckPermissions re-entry is idempotent (sessionStarted and the is Connecting guards prevent duplicate sessions/connections), and the new states/events are wired through with matching tests and previews.

Investigated the github-advanced-security[bot] alerts (3x InlinedApi: ACCESS_LOCAL_NETWORK requires API 37, min is 26): these are false positives. Manifest.permission.ACCESS_LOCAL_NETWORK is a compile-time String constant that is inlined at build time, and every runtime request is gated behind WooPermissionUtils.hasLocalNetworkPermission(), which short-circuits to true when !SystemVersionUtils.isAtLeastCinnamonBun(). So the .launch(ACCESS_LOCAL_NETWORK) calls are never reached below API 37. No code change needed for correctness; if lint runs at error level in CI you may want a baseline entry or a scoped @SuppressLint("InlinedApi") to keep the build green.


Automatic review · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

isLocalNetworkPermissionPermanentlyDenied = !granted && !shouldShowRationale
val currentState = _state.value
if (granted && currentState is WooPosCardReaderConnectionState.MissingLocalNetworkPermission) {
onPhoneConnectClicked(currentState.phone)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review [follow-up]

Issue: When local-network permission is granted (here and in recheckPermissions() on line 214), you reconnect with the phone captured in the MissingLocalNetworkPermission state. While that state is active, shouldIgnoreDiscoveryUpdates() still lets latestDiscoveredPhones refresh, so a phone that re-advertised on a new host/port during the permission prompt leaves the stored address stale. The first connect would then fail and only recover on retry (which does call refreshAddressOf).

Suggestion: Refresh the address before reconnecting, mirroring onPhoneRetryConnectClicked, e.g. onPhoneConnectClicked(latestDiscoveredPhones.refreshAddressOf(currentState.phone)) in both spots.

…etwork-permission

# Conflicts:
#	WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/cardreader/connection/WooPosCardReaderConnectionController.kt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants