diff --git a/Common/Net/URL.cpp b/Common/Net/URL.cpp index 681fc45ff21b..1183022ed7c8 100644 --- a/Common/Net/URL.cpp +++ b/Common/Net/URL.cpp @@ -126,7 +126,7 @@ std::string UriDecode(const std::string & sSrc) const unsigned char * const SRC_END = pSrc + SRC_LEN; const unsigned char * const SRC_LAST_DEC = SRC_END - 2; // last decodable '%' - char * const pStart = new char[SRC_LEN]; + char * const pStart = new char[SRC_LEN]; // Output will be shorter. char * pEnd = pStart; while (pSrc < SRC_LAST_DEC) diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 68c921f7f0b2..375d09a39eb9 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -1111,9 +1111,10 @@ void MainScreen::CreateViews() { TextView *ver = rightColumnItems->Add(new TextView(versionString, new LinearLayoutParams(Margins(70, -6, 0, 0)))); ver->SetSmall(true); ver->SetClip(false); -#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) - rightColumnItems->Add(new Choice(mm->T("Load","Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); -#endif + + if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { + rightColumnItems->Add(new Choice(mm->T("Load", "Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); + } rightColumnItems->Add(new Choice(mm->T("Game Settings", "Settings")))->OnClick.Handle(this, &MainScreen::OnGameSettings); rightColumnItems->Add(new Choice(mm->T("Credits")))->OnClick.Handle(this, &MainScreen::OnCredits); rightColumnItems->Add(new Choice(mm->T("www.ppsspp.org")))->OnClick.Handle(this, &MainScreen::OnPPSSPPOrg); @@ -1214,21 +1215,33 @@ void MainScreen::sendMessage(const char *message, const char *value) { if (!strcmp(message, "browse_folderSelect")) { std::string filename; #if PPSSPP_PLATFORM(ANDROID) + // Hacky way to get a normal path from a Android Storage Framework path. + // Is not gonna work forever, but ship-hack for 1.11. std::string url = value; const char *prefix = "content://com.android.externalstorage.documents/tree/"; + const char *primaryPrefix = "/storage/primary/"; if (startsWith(url, prefix)) { - url = url.substr(strlen(prefix)); - url = UriDecode(url); + url = UriDecode(url.substr(strlen(prefix))); + size_t colonPos = url.find(":"); + if (colonPos != std::string::npos) { + url[colonPos] = '/'; + } + url = "/storage/" + url; + if (startsWith(url, primaryPrefix)) { + url = g_Config.memStickDirectory + url.substr(strlen(primaryPrefix)); + } + INFO_LOG(SYSTEM, "Translated '%s' into '%s'", value, url.c_str()); } else { // It's not gonna work. // TODO: Show an error message? + INFO_LOG(SYSTEM, "Failed to parse content string: '%s'", value); return; } filename = url; #else filename = value; #endif - INFO_LOG(SYSTEM, "Got folder: %s", filename.c_str()); + INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str()); int tab = tabHolder_->GetCurrentTab(); if (tab >= 0 && tab < (int)gameBrowsers_.size()) { gameBrowsers_[tab]->SetPath(filename); diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 6b64d49693d3..90070eedd102 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -429,6 +429,11 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; case SYSPROP_HAS_IMAGE_BROWSER: return true; + case SYSPROP_HAS_FILE_BROWSER: + return false; // We kind of have but needs more work. + case SYSPROP_HAS_FOLDER_BROWSER: + // Uses OPEN_DOCUMENT_TREE to let you select a folder. + return androidVersion >= 21; case SYSPROP_APP_GOLD: #ifdef GOLD return true; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index e4632578c7cb..598bd3849fb8 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -25,6 +25,7 @@ import android.os.Environment; import android.os.PowerManager; import android.os.Vibrator; +import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.InputType; import android.util.Log; @@ -99,7 +100,10 @@ public abstract class NativeActivity extends Activity { // This is to avoid losing the game/menu state etc when we are just // switched-away from or rotated etc. private boolean shuttingDown; + private static final int RESULT_LOAD_IMAGE = 1; + private static final int RESULT_BROWSE_FILE = 2; + private static final int RESULT_OPEN_DOCUMENT_TREE = 3; // Allow for multiple connected gamepads but just consider them the same for now. // Actually this is not entirely true, see the code. @@ -1121,7 +1125,10 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { + if (resultCode != RESULT_OK || data == null) { + return; + } + if (requestCode == RESULT_LOAD_IMAGE) { Uri selectedImage = data.getData(); if (selectedImage != null) { String[] filePathColumn = {MediaStore.Images.Media.DATA}; @@ -1132,6 +1139,20 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { cursor.close(); NativeApp.sendMessage("bgImage_updated", picturePath); } + } else if (requestCode == RESULT_BROWSE_FILE) { + Uri selectedFile = data.getData(); + if (selectedFile != null) { + // NativeApp.sendMessage("br"); + Log.i(TAG, "Browse file finished:" + selectedFile.toString()); + } + } else if (requestCode == RESULT_OPEN_DOCUMENT_TREE) { + Uri selectedFile = data.getData(); + if (selectedFile != null) { + // Convert URI to normal path. (This might not be possible in Android 12+) + String path = selectedFile.toString(); + Log.i(TAG, "Browse folder finished: " + path); + NativeApp.sendMessage("browse_folderSelect", path); + } } } @@ -1263,6 +1284,30 @@ public boolean processCommand(String command, String params) { Log.e(TAG, e.toString()); return false; } + } else if (command.equals("browse_file")) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + //intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); + startActivityForResult(intent, RESULT_BROWSE_FILE); + } catch (Exception e) { + Log.e(TAG, e.toString()); + return false; + } + } else if (command.equals("browse_folder")) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // not yet used properly + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); // Only allow local folders. + startActivityForResult(intent, RESULT_OPEN_DOCUMENT_TREE); + return true; + } catch (Exception e) { + Log.e(TAG, e.toString()); + return false; + } } else if (command.equals("sharejpeg")) { try { Intent share = new Intent(Intent.ACTION_SEND);