Skip to content

Commit fda0e07

Browse files
jamesarichCopilot
andcommitted
feat(connections): wire ACCESS_LOCAL_NETWORK request into Connections screen
On Android 17 (API 37) with targetSdk=37, NsdManager.discoverServices() falls back to the system 'Choose a device to connect' picker on every call when ACCESS_LOCAL_NETWORK isn't granted. Wire the new core/ui permission helpers into ConnectionsScreen so users get a proper one-time grant prompt instead of a per-scan picker. Because ACCESS_LOCAL_NETWORK is in the NEARBY_DEVICES permission group, users who have already granted Bluetooth permissions are silently auto-granted with no UI shown. - Gate ConnectionsScreen network auto-start on the runtime grant so a previously-persisted networkAutoScan=true does not surface the picker for users who haven't granted the permission. - Manual scan toggle now requests the permission before starting and persists the user's intent so the launcher's onGranted callback can start the scan. - Add ScannerViewModel.persistNetworkAutoScanIntent for the deferred-start path. The manifest declaration and core/ui helpers consumed here ship in the separate permission-gating PR (#5211). Verified on Pixel 6a / Android 17 / API 37: NSD discovery now runs without the picker, and the permission is silently auto-granted via the NEARBY_DEVICES group when Bluetooth perms are already held. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 26c0369 commit fda0e07

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ScannerViewModel.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ open class ScannerViewModel(
265265
uiPrefs.setNetworkAutoScan(_isNetworkScanning.value)
266266
}
267267

268+
/**
269+
* Persist the user's intent to auto-scan the network on next screen entry without flipping the active scan flag.
270+
* Used by the Connections screen when it must defer the actual scan start until after the system permission grant
271+
* dialog resolves — the persisted intent ensures auto-start fires once permission is granted.
272+
*/
273+
fun persistNetworkAutoScanIntent(enabled: Boolean) {
274+
uiPrefs.setNetworkAutoScan(enabled)
275+
}
276+
268277
// ── Device selection / disconnect ───────────────────────────────────────────────────────
269278

270279
fun changeDeviceAddress(address: String) {

feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import org.meshtastic.core.ui.component.MainAppBar
6464
import org.meshtastic.core.ui.icon.Language
6565
import org.meshtastic.core.ui.icon.MeshtasticIcons
6666
import org.meshtastic.core.ui.icon.NoDevice
67+
import org.meshtastic.core.ui.util.isLocalNetworkPermissionGranted
68+
import org.meshtastic.core.ui.util.rememberRequestLocalNetworkPermission
6769
import org.meshtastic.core.ui.viewmodel.ConnectionStatus
6870
import org.meshtastic.core.ui.viewmodel.ConnectionsViewModel
6971
import org.meshtastic.feature.connections.MOCK_DEVICE_PREFIX
@@ -118,12 +120,23 @@ fun ConnectionsScreen(
118120

119121
val bleAutoScan by scanModel.bleAutoScan.collectAsStateWithLifecycle()
120122
val networkAutoScan by scanModel.networkAutoScan.collectAsStateWithLifecycle()
123+
val localNetworkPermissionGranted = isLocalNetworkPermissionGranted()
124+
125+
// Android 17 (API 37) gates NSD/mDNS behind ACCESS_LOCAL_NETWORK. Without this prompt the platform
126+
// falls back to the system "Choose a device to connect" picker on every discoverServices() call.
127+
// Granting the permission upfront lets discovery run silently in-app.
128+
val requestLocalNetworkPermission =
129+
rememberRequestLocalNetworkPermission(
130+
onGranted = { scanModel.startNetworkScan() },
131+
onDenied = { scanModel.stopNetworkScan() },
132+
)
121133

122134
// Auto-start scans on screen entry when the user has previously opted in via the toggle. Stop on exit so we don't
123-
// drain battery in the background.
124-
DisposableEffect(Unit) {
135+
// drain battery in the background. Network auto-start is additionally gated on the runtime local-network grant so
136+
// we don't trigger the system picker for users who declined the permission.
137+
DisposableEffect(localNetworkPermissionGranted) {
125138
if (bleAutoScan) scanModel.startBleScan()
126-
if (networkAutoScan) scanModel.startNetworkScan()
139+
if (networkAutoScan && localNetworkPermissionGranted) scanModel.startNetworkScan()
127140
onDispose {
128141
scanModel.stopBleScan()
129142
scanModel.stopNetworkScan()
@@ -263,7 +276,18 @@ fun ConnectionsScreen(
263276
isNetworkScanning = isNetworkScanning,
264277
onSelectDevice = { scanModel.onSelected(it) },
265278
onToggleBleScan = { scanModel.toggleBleScan() },
266-
onToggleNetworkScan = { scanModel.toggleNetworkScan() },
279+
onToggleNetworkScan = {
280+
if (isNetworkScanning || localNetworkPermissionGranted) {
281+
scanModel.toggleNetworkScan()
282+
} else {
283+
// Prefer requesting the runtime grant over letting the platform fall
284+
// back to the system NSD picker. Persist the user's intent so that if
285+
// they grant after the prompt, the scan starts via the launcher's
286+
// onGranted callback and stays on for next session.
287+
scanModel.persistNetworkAutoScanIntent(true)
288+
requestLocalNetworkPermission()
289+
}
290+
},
267291
onAddManualAddress = { _, fullAddress ->
268292
val displayAddress = fullAddress.removePrefix(TCP_DEVICE_PREFIX)
269293
scanModel.addRecentAddress(fullAddress, displayAddress)

0 commit comments

Comments
 (0)