Skip to content

Commit 2bc2c8d

Browse files
alanjhughesmeta-codesync[bot]
authored andcommitted
Add local network permission prompt on android 17 (#57291)
Summary: Android 17 (SDK 37) introduces Local Network Protection. Reaching any local-network address now requires the runtime `ACCESS_LOCAL_NETWORK` permission. This is how the dev server is reached, so on an Android 17 device the dev bundle download is blocked This adds a debug-only path that requests `ACCESS_LOCAL_NETWORK` before the first bundle load and defers `loadApp` until the user answers, so a slow response can't cause an error cc zoontek ## Changelog: [Android] [Fixed] - Request `ACCESS_LOCAL_NETWORK` so the dev server stays reachable on Android 17 (SDK 37)[Android] [Added] - Add `ACCESS_LOCAL_NETWORK` to `PermissionsAndroid` Pull Request resolved: #57291 Test Plan: Validated on an Android 17 emulator and physical device. - **targetSdk 36 and 37** launch → prompt appears → grant → bundle loads. Deny → app starts and shows the normal red-box connection error. - **API 36 / older devices:** no prompt, behavior unchanged. - **USB device via `adb reverse`:** no prompt (loopback is exempt). Reviewed By: cortinico, Abbondanzo Differential Revision: D109160057 Pulled By: fabriziocucci fbshipit-source-id: 4bbd9a3e97e5d12b8add635ed0daa15f6fe90ce0
1 parent 2f325e2 commit 2bc2c8d

7 files changed

Lines changed: 75 additions & 6 deletions

File tree

packages/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ export type Permission =
5858
| 'android.permission.READ_PHONE_NUMBERS'
5959
| 'android.permission.UWB_RANGING'
6060
| 'android.permission.POST_NOTIFICATIONS'
61-
| 'android.permission.NEARBY_WIFI_DEVICES';
61+
| 'android.permission.NEARBY_WIFI_DEVICES'
62+
| 'android.permission.ACCESS_LOCAL_NETWORK';
6263

6364
export type PermissionStatus = 'granted' | 'denied' | 'never_ask_again';
6465

@@ -116,7 +117,8 @@ export interface PermissionsAndroidStatic {
116117
| 'READ_PHONE_NUMBERS'
117118
| 'UWB_RANGING'
118119
| 'POST_NOTIFICATIONS'
119-
| 'NEARBY_WIFI_DEVICES']: Permission;
120+
| 'NEARBY_WIFI_DEVICES'
121+
| 'ACCESS_LOCAL_NETWORK']: Permission;
120122
};
121123
new (): PermissionsAndroidStatic;
122124
/**

packages/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type PermissionsType = Readonly<{
7575
UWB_RANGING: 'android.permission.UWB_RANGING',
7676
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
7777
NEARBY_WIFI_DEVICES: 'android.permission.NEARBY_WIFI_DEVICES',
78+
ACCESS_LOCAL_NETWORK: 'android.permission.ACCESS_LOCAL_NETWORK',
7879
}>;
7980

8081
export type PermissionStatus = 'granted' | 'denied' | 'never_ask_again';
@@ -125,6 +126,7 @@ const PERMISSIONS = Object.freeze({
125126
UWB_RANGING: 'android.permission.UWB_RANGING',
126127
POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
127128
NEARBY_WIFI_DEVICES: 'android.permission.NEARBY_WIFI_DEVICES',
129+
ACCESS_LOCAL_NETWORK: 'android.permission.ACCESS_LOCAL_NETWORK',
128130
}) as PermissionsType;
129131

130132
/**

packages/react-native/ReactAndroid/src/debug/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
-->
88
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
99

10+
<!--
11+
On Android 17 (SDK 37) devices, apps that declare this must hold ACCESS_LOCAL_NETWORK
12+
to reach the dev server over the local network. Loopback via `adb reverse` is exempt.
13+
-->
14+
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK"/>
15+
1016
<application>
1117
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"
1218
android:exported="false" />

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.facebook.react.bridge.Callback;
2323
import com.facebook.react.bridge.ReactContext;
2424
import com.facebook.react.common.LifecycleState;
25+
import com.facebook.react.devsupport.LocalNetworkPermissionUtil;
2526
import com.facebook.react.interfaces.fabric.ReactSurface;
2627
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
2728
import com.facebook.react.modules.core.PermissionListener;
@@ -168,7 +169,8 @@ protected ReactRootView createRootView() {
168169
};
169170
}
170171
if (mainComponentName != null) {
171-
loadApp(mainComponentName);
172+
LocalNetworkPermissionUtil.requestLocalNetworkAccessIfNeeded(
173+
getPlainActivity(), () -> loadApp(mainComponentName));
172174
}
173175
});
174176
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.devsupport
9+
10+
import android.app.Activity
11+
import android.content.pm.PackageManager
12+
import android.os.Build
13+
import com.facebook.react.common.build.ReactBuildConfig
14+
import com.facebook.react.modules.core.PermissionAwareActivity
15+
import com.facebook.react.util.AndroidVersion
16+
17+
/**
18+
* Debug-only helper to request the runtime `ACCESS_LOCAL_NETWORK` permission needed to reach Metro
19+
* on Android 17 (SDK 37) devices, which gate local-network addresses (the emulator's `10.0.2.2`
20+
* alias, a device's Wi-Fi/LAN IP). Requested only in debuggable builds, and always (like iOS),
21+
* since the dev-server host can change at runtime (e.g. switching from `adb reverse` to a LAN IP).
22+
*/
23+
internal object LocalNetworkPermissionUtil {
24+
private const val PERMISSION = "android.permission.ACCESS_LOCAL_NETWORK"
25+
private const val PERMISSION_REQUEST_CODE = 1
26+
27+
/**
28+
* Invokes [onResolved] once it is safe to connect to Metro: immediately when no permission is
29+
* needed, or after the user answers the `ACCESS_LOCAL_NETWORK` prompt otherwise.
30+
*/
31+
@JvmStatic
32+
fun requestLocalNetworkAccessIfNeeded(activity: Activity, onResolved: Runnable) {
33+
if (activity is PermissionAwareActivity && needsLocalNetworkPrompt(activity)) {
34+
activity.requestPermissions(arrayOf(PERMISSION), PERMISSION_REQUEST_CODE) { _, _, _ ->
35+
onResolved.run()
36+
true
37+
}
38+
} else {
39+
onResolved.run()
40+
}
41+
}
42+
43+
/** Whether the `ACCESS_LOCAL_NETWORK` prompt must be shown before reaching the dev server. */
44+
private fun needsLocalNetworkPrompt(activity: Activity): Boolean {
45+
if (!ReactBuildConfig.DEBUG) return false // dev-server only; never prompt in release builds
46+
if (Build.VERSION.SDK_INT < AndroidVersion.VERSION_CODE_CINNAMON_BUN) return false
47+
return activity.checkSelfPermission(PERMISSION) != PackageManager.PERMISSION_GRANTED
48+
}
49+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ internal object AndroidVersion {
2727
*/
2828
internal const val VERSION_CODE_BAKLAVA: Int = 36
2929

30+
/**
31+
* This is the version code for Android 17 (SDK Level 37). Internally at Meta this code is also
32+
* compiled against SDK 34, so we need to retain this constant instead of using
33+
* [Build.VERSION_CODES.CINNAMON_BUN] directly.
34+
*/
35+
internal const val VERSION_CODE_CINNAMON_BUN: Int = 37
36+
3037
/**
3138
* android.R.attr.windowOptOutEdgeToEdgeEnforcement added in API 35. Internally at Meta this code
3239
* is compiled against an SDK that may not have this attribute defined.

packages/react-native/ReactNativeApi.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<6fd7172a54686bbab8a03359976ef63d>>
7+
* @generated SignedSource<<b039d3ad12debfc2af81bc7dc85fb584>>
88
*
99
* This file was generated by scripts/js-api/build-types/index.js.
1010
*/
@@ -3548,6 +3548,7 @@ declare type PermissionsType = {
35483548
readonly ACCESS_BACKGROUND_LOCATION: "android.permission.ACCESS_BACKGROUND_LOCATION"
35493549
readonly ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION"
35503550
readonly ACCESS_FINE_LOCATION: "android.permission.ACCESS_FINE_LOCATION"
3551+
readonly ACCESS_LOCAL_NETWORK: "android.permission.ACCESS_LOCAL_NETWORK"
35513552
readonly ACCESS_MEDIA_LOCATION: "android.permission.ACCESS_MEDIA_LOCATION"
35523553
readonly ACTIVITY_RECOGNITION: "android.permission.ACTIVITY_RECOGNITION"
35533554
readonly ADD_VOICEMAIL: "com.android.voicemail.permission.ADD_VOICEMAIL"
@@ -6053,9 +6054,9 @@ export {
60536054
PanResponderCallbacks, // 6d63e7be
60546055
PanResponderGestureState, // 54baf558
60556056
PanResponderInstance, // 69cebbe8
6056-
Permission, // 06473f4f
6057+
Permission, // 08f1c82f
60576058
PermissionStatus, // 4b7de97b
6058-
PermissionsAndroid, // db2a401e
6059+
PermissionsAndroid, // 8a0bc8d8
60596060
PixelRatio, // 10d9e32d
60606061
Platform, // b73caa89
60616062
PlatformColor, // 8297ec62

0 commit comments

Comments
 (0)