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)