From d2e9e87242b6a8ec074fbfb2d496b0d89503fe1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 16 Oct 2026 14:59:57 +0200 Subject: [PATCH] Android: Ask for the local network permission before starting file upload Part of #21585 Android 17 apps need ACCESS_LOCAL_NETWORK to serve other devices on the LAN. The upload screen now asks for it when it opens, starts the web server once it's granted, and shows a warning and a retry button if it's denied. The permission only exists from API 37, so check for that rather than 35. On Android 15 and 16 it reads as denied, which left Remote ISO's connect tab stuck behind a permission request that could never succeed. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/UploadScreen.cpp | 37 +++++++++++++++++-- UI/UploadScreen.h | 4 ++ .../src/org/ppsspp/ppsspp/PpssppActivity.java | 5 ++- assets/lang/en_US.ini | 2 + 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/UI/UploadScreen.cpp b/UI/UploadScreen.cpp index 0d75d72428..1c8941b5a6 100644 --- a/UI/UploadScreen.cpp +++ b/UI/UploadScreen.cpp @@ -5,6 +5,8 @@ #include "Common/StringUtils.h" #include "Common/Data/Text/Parsers.h" #include "Common/Data/Text/I18n.h" +#include "Common/UI/Notice.h" +#include "Common/System/System.h" #include "Core/WebServer.h" #include "Core/Util/PathUtil.h" #include "UI/UploadScreen.h" @@ -19,12 +21,27 @@ UploadScreen::UploadScreen(const Path &targetFolder) : UISimpleBaseDialogScreen( localIPs_.push_back(ip); } } - WebServerSetUploadPath(targetFolder); - StartWebServer(WebServerFlags::FILE_UPLOAD); + // On Android 17+, serving to other devices on the local network needs a runtime permission. + prevPermission_ = System_GetPermissionStatus(SYSTEM_PERMISSION_LOCAL_NETWORK); + if (prevPermission_ != PERMISSION_STATUS_GRANTED) { + System_AskForPermission(SYSTEM_PERMISSION_LOCAL_NETWORK); + } + StartServerIfPermitted(); } UploadScreen::~UploadScreen() { - StopWebServer(WebServerFlags::FILE_UPLOAD); + if (started_) { + StopWebServer(WebServerFlags::FILE_UPLOAD); + } +} + +void UploadScreen::StartServerIfPermitted() { + if (started_ || System_GetPermissionStatus(SYSTEM_PERMISSION_LOCAL_NETWORK) != PERMISSION_STATUS_GRANTED) { + return; + } + WebServerSetUploadPath(targetFolder_); + StartWebServer(WebServerFlags::FILE_UPLOAD); + started_ = true; } void UploadScreen::CreateDialogViews(UI::ViewGroup *root) { @@ -37,7 +54,12 @@ void UploadScreen::CreateDialogViews(UI::ViewGroup *root) { container->Add(new TextWithImage(ImageID("I_FOLDER_UPLOAD"), GetFriendlyPath(targetFolder_))); container->Add(new Spacer(20.0f)); - if (prevRunning_) { + if (prevPermission_ == PERMISSION_STATUS_DENIED) { + container->Add(new NoticeView(NoticeLevel::WARN, n->T("Local network permission is needed to receive files"), ""))->SetWrapText(true); + container->Add(new Choice(n->T("Ask for network permission")))->OnClick.Add([](UI::EventParams &e) { + System_AskForPermission(SYSTEM_PERMISSION_LOCAL_NETWORK); + }); + } else if (prevRunning_) { container->Add(new TextWithImage(ImageID("I_WIFI"), n->T("With a web browser on the same network, go to:"))); for (const auto &ip : localIPs_) { std::string url = StringFromFormat("%s:%d/upload", ip.c_str(), WebServerPort()); @@ -74,6 +96,13 @@ std::string_view UploadScreen::GetTitle() const { void UploadScreen::update() { UISimpleBaseDialogScreen::update(); + PermissionStatus permission = System_GetPermissionStatus(SYSTEM_PERMISSION_LOCAL_NETWORK); + if (permission != prevPermission_) { + prevPermission_ = permission; + StartServerIfPermitted(); + RecreateViews(); + } + bool running = WebServerRunning(WebServerFlags::FILE_UPLOAD); if (prevRunning_ != running) { prevRunning_ = running; diff --git a/UI/UploadScreen.h b/UI/UploadScreen.h index 7db4b0d13c..ef43bed11c 100644 --- a/UI/UploadScreen.h +++ b/UI/UploadScreen.h @@ -28,6 +28,7 @@ #include "UI/BaseScreens.h" #include "UI/SimpleDialogScreen.h" #include "Common/TimeUtil.h" +#include "Common/System/System.h" // Upload screen: Shows the user an ip address to go to in a web browser on the same network, // in order to upload game files to the current directory. @@ -46,6 +47,9 @@ protected: private: void RecreateStatus(); + void StartServerIfPermitted(); + bool started_ = false; + PermissionStatus prevPermission_ = PERMISSION_STATUS_UNKNOWN; bool prevRunning_ = false; std::vector localIPs_; Path targetFolder_; diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 24492aef11..5ba5838a9a 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -249,7 +249,8 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList NativeApp.sendMessageFromJava("permission_denied", "storage"); } - if (Build.VERSION.SDK_INT >= 35) { + // ACCESS_LOCAL_NETWORK exists from Android 17 (API 37). Earlier versions report it as denied. + if (Build.VERSION.SDK_INT >= 37) { if (this.checkSelfPermission("android.permission.ACCESS_LOCAL_NETWORK") == PackageManager.PERMISSION_GRANTED) { NativeApp.sendMessageFromJava("permission_granted", "local_network"); } else { @@ -1828,7 +1829,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } return true; } else if (command.equals("ask_permission") && params.equals("local_network")) { - if (Build.VERSION.SDK_INT >= 35) { + if (Build.VERSION.SDK_INT >= 37) { if (askForPermissions(permissionsForLocalNetwork, REQUEST_CODE_LOCAL_NETWORK_PERMISSION)) { NativeApp.sendMessageFromJava("permission_pending", "local_network"); } else { diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 83d2da7b4d..f8ed3ffad4 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -985,6 +985,7 @@ Ad hoc server address = Ad hoc server address AdhocServer Failed to Bind Port = Ad hoc server failed to bind port Allow speed control while connected (not recommended) = Allow speed control while connected (not recommended) AM: Data from Unknown Port = AM: Data from Unknown Port +Ask for network permission = Ask for network permission Auto = Auto Autoconfigure = Autoconfigure ChangeMacSaveConfirm = Generate a new MAC address? @@ -1020,6 +1021,7 @@ Infrastructure = Infrastructure Infrastructure server provided by: = Infrastructure server provided by: Invalid IP or hostname = Invalid IP or hostname Local network addresses = Local network addresses +Local network permission is needed to receive files = Local network permission is needed to receive files MAC address = MAC address Minimum Timeout = Minimum timeout (override in ms, 0 = default) Misc = Miscellaneous (default = PSP compatibility)