From 793e79945fe81a02331906e6cfb8cd6065881f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Jul 2021 19:50:36 +0200 Subject: [PATCH 01/15] Enable scoped storage enforcement on Android 11+. This has a number of UX issues and bugs we need to work through, but at least games are playable, things mostly work. Upgrades are handled smoothly by keeping existing storage access until you uninstall. After a reinstall, you'll need to re-select your old PSP directory manually in settings :( --- UI/DevScreens.cpp | 6 ++-- UI/MainScreen.cpp | 3 +- android/AndroidManifest.xml | 8 +++-- android/build.gradle | 6 ++-- android/jni/app-android.cpp | 34 ++++++++++++++++--- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 1 + 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 735ae47928..81f17d589f 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -23,6 +23,7 @@ #include "Common/System/NativeApp.h" #include "Common/System/System.h" #include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/File/AndroidStorage.h" #include "Common/Data/Text/I18n.h" #include "Common/Net/HTTPClient.h" #include "Common/UI/Context.h" @@ -59,12 +60,8 @@ int GetD3DCompilerVersion(); #endif -#if PPSSPP_PLATFORM(ANDROID) - #include "android/jni/app-android.h" -#endif - static const char *logLevelList[] = { "Notice", "Error", @@ -595,6 +592,7 @@ void SystemInfoScreen::CreateViews() { if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { storage->Add(new InfoItem("Scoped Storage", di->T("Yes"))); } + storage->Add(new InfoItem("IsStoragePreservedLegacy", Android_IsExternalStoragePreservedLegacy() ? di->T("Yes") : di->T("No"))); #endif ViewGroup *buildConfigScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 6f7872289a..bc07677ac5 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -978,7 +978,8 @@ void MainScreen::CreateViews() { tabHolder_->SetClip(true); bool showRecent = g_Config.iMaxRecent > 0; - bool hasStorageAccess = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; + bool hasStorageAccess = !System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS) || + System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; bool storageIsTemporary = IsTempPath(GetSysDirectory(DIRECTORY_SAVEDATA)) && !confirmedTemporary_; if (showRecent && !hasStorageAccess) { showRecent = !g_Config.recentIsos.empty(); diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 002a779cc4..9c2477efa2 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -19,8 +19,8 @@ - - + + @@ -45,7 +45,9 @@ android:logo="@drawable/ic_banner" android:isGame="true" android:banner="@drawable/tv_banner" - android:requestLegacyExternalStorage="true"> + android:requestLegacyExternalStorage="true" + android:preserveLegacyExternalStorage="true" + > = 14000000) { @@ -61,7 +61,7 @@ android { new File("versioncode.txt").write(androidGitVersion.code().toString()) minSdkVersion 9 - targetSdkVersion 29 + targetSdkVersion 30 if (project.hasProperty("ANDROID_VERSION_CODE") && project.hasProperty("ANDROID_VERSION_NAME")) { versionCode ANDROID_VERSION_CODE versionName ANDROID_VERSION_NAME diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 0cfe1f4e19..fb2723f9c1 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -97,8 +97,18 @@ struct JNIEnv {}; bool useCPUThread = true; -// We'll turn this on when we target Android 12. -bool useScopedStorageIfRequired = false; +// We turn this on now that when we target Android 11+. +// Along with adding: +// android:preserveLegacyExternalStorage="true" +// To the already requested: +// android:requestLegacyExternalStorage="true" +// +// This will cause Android 11+ to still behave like Android 10 until the app +// is manually uninstalled. We can detect this state with +// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see +// that scoped storage enforcement is disabled in this case. + +static const bool useScopedStorageIfRequired = true; enum class EmuThreadState { DISABLED, @@ -433,7 +443,15 @@ float System_GetPropertyFloat(SystemProperty prop) { bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_SUPPORTS_PERMISSIONS: - return androidVersion >= 23; // 6.0 Marshmallow introduced run time permissions. + if (androidVersion < 23) { + // 6.0 Marshmallow introduced run time permissions. + return false; + } else { + // It gets a bit complicated here. If scoped storage enforcement is on, + // we also don't need to request permissions. We'll have the access we request + // on a per-folder basis. + return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); + } case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE: return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature. case SYSPROP_HAS_ADDITIONAL_STORAGE: @@ -458,8 +476,14 @@ bool System_GetPropertyBool(SystemProperty prop) { case SYSPROP_CAN_JIT: return true; case SYSPROP_ANDROID_SCOPED_STORAGE: - if (useScopedStorageIfRequired && androidVersion >= 28) - return true; + if (useScopedStorageIfRequired && androidVersion >= 28) { + // Here we do a check to see if we ended up in the preserveLegacyExternalStorage path. + // That won't last if the user uninstalls/reinstalls though, but would preserve the user + // experience for simple upgrades so maybe let's support it. + return !Android_IsExternalStoragePreservedLegacy(); + } else { + return false; + } default: return false; } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 761cc156ab..f7786ef707 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.util.Log; From 87bcdb7b88d70842bb9edf1b681560de9385877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Jul 2021 15:33:11 +0200 Subject: [PATCH 02/15] Work on the new experience --- Core/Config.cpp | 11 ++- Core/Config.h | 2 +- UI/GameSettingsScreen.cpp | 2 +- UI/MemStickScreen.cpp | 164 ++++++++++++++++++++++---------------- UI/MemStickScreen.h | 16 +++- UI/NativeApp.cpp | 44 +++++----- 6 files changed, 141 insertions(+), 98 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index d97e2509cd..7c35cd89de 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1412,12 +1412,14 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { INFO_LOG(LOADER, "Config loaded: '%s'", iniFilename_.c_str()); } -void Config::Save(const char *saveReason) { +bool Config::Save(const char *saveReason) { if (!IsFirstInstance()) { // TODO: Should we allow saving config if started from a different directory? // How do we tell? WARN_LOG(LOADER, "Not saving config - secondary instances don't."); - return; + + // Don't want to retry or something. + return true; } if (jitForcedOff) { @@ -1488,7 +1490,7 @@ void Config::Save(const char *saveReason) { if (!iniFile.Save(iniFilename_)) { ERROR_LOG(LOADER, "Error saving config (%s)- can't write ini '%s'", saveReason, iniFilename_.c_str()); System_SendMessage("toast", "Failed to save settings!\nCheck permissions, or try to restart the device."); - return; + return false; } INFO_LOG(LOADER, "Config saved (%s): '%s'", saveReason, iniFilename_.c_str()); @@ -1501,7 +1503,7 @@ void Config::Save(const char *saveReason) { KeyMap::SaveToIni(controllerIniFile); if (!controllerIniFile.Save(controllerIniFilename_)) { ERROR_LOG(LOADER, "Error saving config - can't write ini '%s'", controllerIniFilename_.c_str()); - return; + return false; } INFO_LOG(LOADER, "Controller config saved: %s", controllerIniFilename_.c_str()); } @@ -1512,6 +1514,7 @@ void Config::Save(const char *saveReason) { // force JIT off again just in case Config::Save() is called without exiting PPSSPP g_Config.iCpuCore = (int)CPUCore::INTERPRETER; } + return true; } // Use for debugging the version check without messing with the server diff --git a/Core/Config.h b/Core/Config.h index a07a6182d8..f5dcd9ad44 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -490,7 +490,7 @@ public: std::string dismissedVersion; void Load(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr); - void Save(const char *saveReason); + bool Save(const char *saveReason); void Reload(); void RestoreDefaults(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 996638b294..71006d005b 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1097,7 +1097,7 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { - screenManager()->push(new MemStickScreen()); + screenManager()->push(new MemStickScreen(false)); return UI::EVENT_DONE; } diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 6759457f39..beee406b99 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -25,23 +25,26 @@ #include "Common/UI/ViewGroup.h" #include "Common/StringUtils.h" -#include "Common/File/FileUtil.h" -#include "Common/File/Path.h" #include "Common/System/System.h" #include "Common/System/NativeApp.h" -#include "Common/Data/Text/I18n.h" #include "Common/System/Display.h" +#include "Common/Data/Text/I18n.h" + +#include "Common/File/AndroidStorage.h" +#include "Common/File/FileUtil.h" +#include "Common/File/Path.h" #include "Core/Util/GameManager.h" #include "Core/System.h" +#include "Core/Config.h" #include "UI/MemStickScreen.h" #include "UI/MainScreen.h" #include "UI/MiscScreens.h" -#include "Core/Config.h" - -MemStickScreen::~MemStickScreen() { } +MemStickScreen::~MemStickScreen() { + pendingMemStickFolder_ = g_Config.memStickDirectory; +} void MemStickScreen::CreateViews() { using namespace UI; @@ -51,45 +54,68 @@ void MemStickScreen::CreateViews() { Margins actionMenuMargins(15, 15, 15, 0); - root_ = new AnchorLayout(); + root_ = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)); - ViewGroup *columns = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)); - root_->Add(columns); - - ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0)); + Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f)); + ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); - columns->Add(leftColumn); - columns->Add(rightColumnItems); + root_->Add(spacerColumn); + root_->Add(leftColumn); + root_->Add(rightColumnItems); - Path path = g_Config.memStickDirectory; - int64_t freeSpaceAtMemStick = -1; -#if PPSSPP_PLATFORM(ANDROID) - if (Android_IsContentUri(path.ToString())) { - freeSpaceAtMemStick = Android_GetFreeSpaceByContentUri(path.ToString()); - } else { - freeSpaceAtMemStick = Android_GetFreeSpaceByFilePath(path.ToString()); + if (initialSetup_) { + leftColumn->Add(new TextView(iz->T("Welcome to PPSSPP!"), ALIGN_LEFT, false)); + leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); + } + + leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose PSP data storage (Memory Stick)"), ALIGN_LEFT, false)); + +#if !PPSSPP_PLATFORM(WINDOWS) + if (!pendingMemStickFolder_.empty()) { + int64_t freeSpaceAtMemStick = -1; + if (Android_IsContentUri(pendingMemStickFolder_.ToString())) { + freeSpaceAtMemStick = Android_GetFreeSpaceByContentUri(pendingMemStickFolder_.ToString()); + } else { + freeSpaceAtMemStick = Android_GetFreeSpaceByFilePath(pendingMemStickFolder_.ToString()); + } + + leftColumn->Add(new TextView(pendingMemStickFolder_.ToVisualString(), ALIGN_LEFT, false)); + std::string freeSpaceText = "Free space: N/A"; + if (freeSpaceAtMemStick >= 0) { + freeSpaceText = StringFromFormat("free space: %lld MB", freeSpaceAtMemStick / (1024 * 1024)); + leftColumn->Add(new TextView(freeSpaceText, ALIGN_LEFT, false)); + } + } + + if (!g_Config.memStickDirectory.empty()) { + TextView *view = leftColumn->Add(new TextView(g_Config.memStickDirectory.ToVisualString(), ALIGN_LEFT, false)); + view->SetShadow(true); } #endif - int leftSide = 100; - settingInfo_ = new SettingInfoMessage(ALIGN_CENTER | FLAG_WRAP_TEXT, new AnchorLayoutParams(dp_xres - leftSide - 40.0f, WRAP_CONTENT, leftSide, dp_yres - 80.0f - 40.0f, NONE, NONE)); - settingInfo_->SetBottomCutoff(dp_yres - 200.0f); + leftColumn->Add(new Choice(iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnBrowse); + leftColumn->Add(new TextView(iz->T("ChooseFolderDesc", "* Data will stay even if you uninstall PPSSPP.\n* Data can be shared with PPSSPP Gold\n* Easy USB access"), ALIGN_LEFT, false)); - root_->Add(settingInfo_); + leftColumn->Add(new Choice(iz->T("Use App Private Directory")))->OnClick.Handle(this, &MemStickScreen::OnUseInternalStorage); + leftColumn->Add(new TextView(iz->T("InternalStorageDesc", "* Warning! Data will be deleted if you uninstall PPSSPP!\n* Data cannot be shared with PPSSPP Gold\n* USB access through Android/data/org.ppsspp.ppsspp/files"), ALIGN_LEFT, false)); - leftColumn->Add(new TextView(iz->T("Memory Stick Storage"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); - leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose where your PSP memory stick data (savegames, etc) is stored"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 50, NONE, NONE))); - leftColumn->Add(new TextView(g_Config.memStickDirectory.ToVisualString(), ALIGN_LEFT, false, new AnchorLayoutParams(10, 140, NONE, NONE))); + leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); - std::string freeSpaceText = "Free space: N/A"; - if (freeSpaceAtMemStick >= 0) { - freeSpaceText = StringFromFormat("free space: %lld MB", freeSpaceAtMemStick / (1024 * 1024)); + Choice *confirmButton = rightColumnItems->Add(new Choice(iz->T("Confirm"))); + confirmButton->OnClick.Handle(this, &MemStickScreen::OnConfirm); + confirmButton->SetEnabled(!pendingMemStickFolder_.empty()); + + if (!initialSetup_) { + rightColumnItems->Add(new CheckBox(&moveData_, iz->T("Move Data"))); + rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnOK); } - leftColumn->Add(new TextView(freeSpaceText, ALIGN_LEFT, false, new AnchorLayoutParams(10, 240, NONE, NONE))); + INFO_LOG(SYSTEM, "MemStickScreen: initialSetup=%d", (int)initialSetup_); +} - rightColumnItems->Add(new Choice(iz->T("Browse")))->OnClick.Handle(this, &MemStickScreen::OnBrowse); - rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnOK); +UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { + pendingMemStickFolder_ = Path(g_extFilesDir); + return UI::EVENT_DONE; } UI::EventReturn MemStickScreen::OnBrowse(UI::EventParams ¶ms) { @@ -108,48 +134,52 @@ void MemStickScreen::sendMessage(const char *message, const char *value) { filename = value; INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str()); pendingMemStickFolder_ = Path(filename); - CallbackMemStickFolder(true); + RecreateViews(); } } } -void MemStickScreen::CallbackMemStickFolder(bool yes) { +UI::EventReturn MemStickScreen::OnConfirm(UI::EventParams ¶ms) { auto sy = GetI18NCategory("System"); - if (yes) { - Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; - Path testWriteFile = pendingMemStickFolder_ / ".write_verify_file"; + Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; + Path testWriteFile = pendingMemStickFolder_ / ".write_verify_file"; - // Doesn't already exist, create. - // Should this ever happen? - if (pendingMemStickFolder_.Type() == PathType::NATIVE) { - if (!File::Exists(pendingMemStickFolder_)) { - File::CreateFullPath(pendingMemStickFolder_); - } - if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { - settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); - return; - } - File::Delete(testWriteFile); - } else { - // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse - // for folder, we can assume it exists, barring wacky race conditions like the user being connected - // by USB and deleting it. + // Doesn't already exist, create. + // Should this ever happen? + if (pendingMemStickFolder_.Type() == PathType::NATIVE) { + if (!File::Exists(pendingMemStickFolder_)) { + File::CreateFullPath(pendingMemStickFolder_); } - - // This doesn't need the storage API - this path is accessible the normal way. - std::string str = pendingMemStickFolder_.ToString(); - if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { - ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", pendingMemStickFolder_.c_str(), memStickDirFile.c_str()); - // Not sure what to do if this file. + if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { + // settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + return UI::EVENT_DONE; } - - // Save so the settings, at least, are transferred. - g_Config.memStickDirectory = pendingMemStickFolder_; - g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); - g_Config.UpdateIniLocation(); - - g_Config.Save("MemstickPathChanged"); - screenManager()->RecreateAllViews(); + File::Delete(testWriteFile); + } else { + // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse + // for folder, we can assume it exists and is writable, barring wacky race conditions like the user + // being connected by USB and deleting it. } + + // This doesn't need the storage API - this path is accessible the normal way. + std::string str = pendingMemStickFolder_.ToString(); + if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { + ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", pendingMemStickFolder_.c_str(), memStickDirFile.c_str()); + // Not sure what to do if this file. + } + + // Save so the settings, at least, are transferred. + g_Config.memStickDirectory = pendingMemStickFolder_; + g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); + g_Config.UpdateIniLocation(); + + if (g_Config.Save("MemstickPathChanged")) { + TriggerFinish(DialogResult::DR_OK); + } else { + error_ = sy->T("Failed to save config"); + RecreateViews(); + } + + return UI::EVENT_DONE; } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index b0c4f55dee..7f7ccaafa2 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -30,19 +30,27 @@ // Currently only useful for Android. class MemStickScreen : public UIDialogScreenWithBackground { public: - MemStickScreen() {} + MemStickScreen(bool initialSetup) + : initialSetup_(initialSetup) {} virtual ~MemStickScreen(); std::string tag() const override { return "game"; } + void CreateViews() override; protected: - void CreateViews() override; - void CallbackMemStickFolder(bool yes); void sendMessage(const char *message, const char *value) override; private: // Event handlers UI::EventReturn OnBrowse(UI::EventParams &e); + UI::EventReturn OnConfirm(UI::EventParams ¶ms); + UI::EventReturn OnUseInternalStorage(UI::EventParams ¶ms); + Path pendingMemStickFolder_; - SettingInfoMessage *settingInfo_; + SettingInfoMessage *settingInfo_ = nullptr; + + std::string error_; + + bool initialSetup_; + bool moveData_ = true; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 5fb7f3a8de..8925f0485c 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -108,6 +108,7 @@ #include "UI/GPUDriverTestScreen.h" #include "UI/HostTypes.h" #include "UI/MiscScreens.h" +#include "UI/MemStickScreen.h" #include "UI/OnScreenDisplay.h" #include "UI/RemoteISOScreen.h" #include "UI/TiltEventProcessor.h" @@ -338,8 +339,6 @@ static void PostLoadConfig() { i18nrepo.LoadIni(g_Config.sLanguageIni); else i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath); - - g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count); } static bool CreateDirectoriesAndroid() { @@ -436,7 +435,7 @@ static void ClearFailedGPUBackends() { // We've successfully started graphics without crashing, hurray. // In case they update drivers and have totally different problems much later, clear the failed list. g_Config.sFailedGPUBackends.clear(); - if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) { + if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS) || System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { File::Delete(GetSysDirectory(DIRECTORY_APP_CACHE) / "FailedGraphicsBackends.txt"); } else { g_Config.Save("clearFailedGPUBackends"); @@ -449,6 +448,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch ShaderTranslationInit(); InitFastMath(cpu_info.bNEON); + g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count); + SetupAudioFormats(); g_Discord.SetPresenceMenu(); @@ -504,22 +505,22 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // is no longer the plain root of external storage, but it's an app specific directory // on external storage (g_extFilesDir). if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - g_Config.defaultCurrentDirectory = Path(g_extFilesDir); + // There's no sensible default directory. Let the user browse for files. + g_Config.defaultCurrentDirectory.clear(); } else { - // Maybe there should be an option to use internal memory instead, but I think - // that for most people, using external memory (SDCard/USB Storage) makes the - // most sense. + g_Config.memStickDirectory = Path(external_dir); g_Config.defaultCurrentDirectory = Path(external_dir); + CreateDirectoriesAndroid(); } // Might also add an option to move it to internal / non-visible storage, but there's // little point, really. - g_Config.memStickDirectory = Path(external_dir); g_Config.flash0Directory = Path(external_dir) / "flash0"; Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; if (File::Exists(memstickDirFile)) { + INFO_LOG(SYSTEM, "Reading '%s' to find memstick dir.", memstickDirFile.c_str()); std::string memstickDir; if (File::ReadFileToString(true, memstickDirFile, memstickDir)) { Path memstickPath(memstickDir); @@ -529,15 +530,14 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } else { ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str()); if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - // TODO: Gotta resolve this somehow... - // I think we wanna pop up the memstick dir chooser before any other screen in this case. - // For now we just choose a default. - g_Config.memStickDirectory = g_Config.defaultCurrentDirectory; + // Ask the user to configure a memstick directory. + INFO_LOG(SYSTEM, "Asking the user."); + g_Config.memStickDirectory.clear(); } } } } else { - INFO_LOG(SYSTEM, "No memstick directory file found. Using '%s'", memstickDirFile.c_str()); + INFO_LOG(SYSTEM, "No memstick directory file found (tried to open '%s')", memstickDirFile.c_str()); } #elif PPSSPP_PLATFORM(IOS) @@ -572,8 +572,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch DiskCachingFileLoaderCache::SetCacheDir(g_Config.appCacheDirectory); } - if (!LogManager::GetInstance()) + if (!LogManager::GetInstance()) { LogManager::Init(&g_Config.bEnableLogging); + } #if !PPSSPP_PLATFORM(WINDOWS) g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); @@ -585,11 +586,6 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch LogManager *logman = LogManager::GetInstance(); -#if PPSSPP_PLATFORM(ANDROID) - // On early versions of Android we don't need to ask permission. - CreateDirectoriesAndroid(); -#endif - const char *fileToLog = 0; Path stateToLoad; @@ -785,8 +781,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch }); } + INFO_LOG(SYSTEM, "ScreenManager!"); screenManager = new ScreenManager(); - if (gotoGameSettings) { + if (g_Config.memStickDirectory.empty()) { + INFO_LOG(SYSTEM, "No memstick directory! Asking for one to be configured."); + screenManager->switchScreen(new MainScreen()); + screenManager->push(new MemStickScreen(true)); + } else if (gotoGameSettings) { screenManager->switchScreen(new LogoScreen(true)); } else if (gotoTouchScreenTest) { screenManager->switchScreen(new MainScreen()); @@ -832,7 +833,6 @@ static UI::Style MakeStyle(uint32_t fg, uint32_t bg) { UI::Style s; s.background = UI::Drawable(bg); s.fgColor = fg; - return s; } @@ -1477,6 +1477,8 @@ void NativeShutdown() { logger = nullptr; } + g_threadManager.Teardown(); + // Previously we did exit() here on Android but that makes it hard to do things like restart on backend change. // I think we handle most globals correctly or correct-enough now. } From 3be1ff6ce486a079c9d61eeee7325e2a73004ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Jul 2021 00:16:30 +0200 Subject: [PATCH 03/15] Get the new setup flow going, with a confirmation dialog etc. Moving the folder while already installed still crashes. --- Common/File/DiskFree.cpp | 4 +- Common/File/DiskFree.h | 3 +- Common/File/Path.cpp | 1 - Common/File/PathBrowser.h | 4 + Common/System/NativeApp.h | 2 + Common/UI/Screen.cpp | 3 + Core/FileLoaders/DiskCachingFileLoader.cpp | 4 +- Core/FileSystems/DirectoryFileSystem.cpp | 4 +- Core/HLE/sceCtrl.cpp | 6 +- UI/MemStickScreen.cpp | 301 +++++++++++++++++---- UI/MemStickScreen.h | 28 +- UI/NativeApp.cpp | 21 +- 12 files changed, 298 insertions(+), 83 deletions(-) diff --git a/Common/File/DiskFree.cpp b/Common/File/DiskFree.cpp index 3a5fb9e722..f1412a35be 100644 --- a/Common/File/DiskFree.cpp +++ b/Common/File/DiskFree.cpp @@ -23,7 +23,7 @@ #include "Common/File/AndroidStorage.h" #include "Common/Data/Encoding/Utf8.h" -bool free_disk_space(const Path &path, uint64_t &space) { +bool free_disk_space(const Path &path, int64_t &space) { #ifdef _WIN32 ULARGE_INTEGER free; if (GetDiskFreeSpaceExW(path.ToWString().c_str(), &free, nullptr, nullptr)) { @@ -43,7 +43,7 @@ bool free_disk_space(const Path &path, uint64_t &space) { if (res == 0) { #ifndef __ANDROID__ if (diskstat.f_flag & ST_RDONLY) { - space = 0; + space = -1; return true; } #endif diff --git a/Common/File/DiskFree.h b/Common/File/DiskFree.h index 5405fc4dc4..a08f44bdd1 100644 --- a/Common/File/DiskFree.h +++ b/Common/File/DiskFree.h @@ -4,4 +4,5 @@ #include "Common/File/Path.h" -bool free_disk_space(const Path &path, uint64_t &space); +// If this fails, false is returned and space is negative. +bool free_disk_space(const Path &path, int64_t &space); diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index 5f0e905dda..86a43f20bd 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -236,7 +236,6 @@ bool Path::CanNavigateUp() const { if (type_ == PathType::CONTENT_URI) { return AndroidContentURI(path_).CanNavigateUp(); } - if (path_ == "/" || path_ == "") { return false; } diff --git a/Common/File/PathBrowser.h b/Common/File/PathBrowser.h index dd8cdeebb2..9f10cde560 100644 --- a/Common/File/PathBrowser.h +++ b/Common/File/PathBrowser.h @@ -35,6 +35,10 @@ public: } std::string GetFriendlyPath() const; + bool empty() const { + return path_.empty(); + } + private: void HandlePath(); void ResetPending(); diff --git a/Common/System/NativeApp.h b/Common/System/NativeApp.h index d501d03b43..d911fd9076 100644 --- a/Common/System/NativeApp.h +++ b/Common/System/NativeApp.h @@ -89,3 +89,5 @@ void NativeSetMixer(void* mixer); // Main thread. void NativeShutdownGraphics(); void NativeShutdown(); + +void PostLoadConfig(); diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp index 1945b5be4f..ea5ed185f5 100644 --- a/Common/UI/Screen.cpp +++ b/Common/UI/Screen.cpp @@ -163,6 +163,8 @@ void ScreenManager::render() { iter--; Layer backback = *iter; + _assert_(backback.screen); + // TODO: Make really sure that this "mismatched" pre/post only happens // when screens are "compatible" (both are UIScreens, for example). backback.screen->preRender(); @@ -174,6 +176,7 @@ void ScreenManager::render() { break; } default: + _assert_(stack_.back().screen); stack_.back().screen->preRender(); stack_.back().screen->render(); if (postRenderCb_) diff --git a/Core/FileLoaders/DiskCachingFileLoader.cpp b/Core/FileLoaders/DiskCachingFileLoader.cpp index 06b140e0e5..73ba1ca868 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.cpp +++ b/Core/FileLoaders/DiskCachingFileLoader.cpp @@ -754,9 +754,9 @@ u64 DiskCachingFileLoaderCache::FreeDiskSpace() { dir = GetSysDirectory(DIRECTORY_CACHE); } - uint64_t result = 0; + int64_t result = 0; if (free_disk_space(dir, result)) { - return result; + return (u64)result; } // We can't know for sure how much is free, so we have to assume none. diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 4e2ab9b2d8..528654a045 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -925,9 +925,9 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { } u64 DirectoryFileSystem::FreeSpace(const std::string &path) { - uint64_t result = 0; + int64_t result = 0; if (free_disk_space(GetLocalPath(path), result)) { - return ReplayApplyDisk64(ReplayAction::FREESPACE, result, CoreTiming::GetGlobalTimeUs()); + return ReplayApplyDisk64(ReplayAction::FREESPACE, (uint64_t)result, CoreTiming::GetGlobalTimeUs()); } #if HOST_IS_CASE_SENSITIVE diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index fe78433792..78dee49bda 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -501,7 +501,8 @@ static int sceCtrlReadBufferNegative(u32 ctrlDataPtr, u32 nBufs) static int sceCtrlPeekBufferPositive(u32 ctrlDataPtr, u32 nBufs) { int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, false, true); - DEBUG_LOG(SCECTRL, "%d=sceCtrlPeekBufferPositive(%08x, %i)", done, ctrlDataPtr, nBufs); + // Some homebrew call this in a tight loop - so VERBOSE it is. + VERBOSE_LOG(SCECTRL, "%d=sceCtrlPeekBufferPositive(%08x, %i)", done, ctrlDataPtr, nBufs); hleEatCycles(330); return done; } @@ -509,7 +510,8 @@ static int sceCtrlPeekBufferPositive(u32 ctrlDataPtr, u32 nBufs) static int sceCtrlPeekBufferNegative(u32 ctrlDataPtr, u32 nBufs) { int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, true, true); - DEBUG_LOG(SCECTRL, "%d=sceCtrlPeekBufferNegative(%08x, %i)", done, ctrlDataPtr, nBufs); + // Some homebrew call this in a tight loop - so VERBOSE it is. + VERBOSE_LOG(SCECTRL, "%d=sceCtrlPeekBufferNegative(%08x, %i)", done, ctrlDataPtr, nBufs); hleEatCycles(330); return done; } diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index beee406b99..0e62fd644d 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -33,6 +33,7 @@ #include "Common/File/AndroidStorage.h" #include "Common/File/FileUtil.h" #include "Common/File/Path.h" +#include "Common/File/DiskFree.h" #include "Core/Util/GameManager.h" #include "Core/System.h" @@ -42,7 +43,63 @@ #include "UI/MainScreen.h" #include "UI/MiscScreens.h" -MemStickScreen::~MemStickScreen() { +static bool FolderSeemsToBeUsed(Path newMemstickFolder) { + // Inspect the potential new folder. + if (File::Exists(newMemstickFolder / "PSP") || File::Exists(newMemstickFolder / "SYSTEM")) { + // Does seem likely. We could add more critera like checking for actual savegames or something. + return true; + } else { + return false; + } +} + +static bool SwitchMemstickFolderTo(Path newMemstickFolder) { + Path testWriteFile = newMemstickFolder / ".write_verify_file"; + + // Doesn't already exist, create. + // Should this ever happen? + if (newMemstickFolder.Type() == PathType::NATIVE) { + if (!File::Exists(newMemstickFolder)) { + File::CreateFullPath(newMemstickFolder); + } + if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { + // settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + // TODO: Display an error! + return UI::EVENT_DONE; + } + File::Delete(testWriteFile); + } else { + // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse + // for folder, we can assume it exists and is writable, barring wacky race conditions like the user + // being connected by USB and deleting it. + } + + Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; + std::string str = newMemstickFolder.ToString(); + if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { + ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", newMemstickFolder.c_str(), memStickDirFile.c_str()); + // Not sure what to do if this file. + } + + // Save so the settings, at least, are transferred. + g_Config.memStickDirectory = newMemstickFolder; + g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); + g_Config.UpdateIniLocation(); + + return true; +} + +static std::string FormatSpaceString(int64_t space) { + if (space >= 0) { + // TODO: Smarter display (MB, GB as appropriate). Don't we have one of these somewhere? + return StringFromFormat("%lld MB", space / (1024 * 1024)); + } else { + return "N/A"; + } +} + +MemStickScreen::MemStickScreen(bool initialSetup) + : initialSetup_(initialSetup) { pendingMemStickFolder_ = g_Config.memStickDirectory; } @@ -54,7 +111,7 @@ void MemStickScreen::CreateViews() { Margins actionMenuMargins(15, 15, 15, 0); - root_ = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)); + root_ = new LinearLayout(ORIENT_HORIZONTAL); Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f)); ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); @@ -70,29 +127,6 @@ void MemStickScreen::CreateViews() { leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose PSP data storage (Memory Stick)"), ALIGN_LEFT, false)); -#if !PPSSPP_PLATFORM(WINDOWS) - if (!pendingMemStickFolder_.empty()) { - int64_t freeSpaceAtMemStick = -1; - if (Android_IsContentUri(pendingMemStickFolder_.ToString())) { - freeSpaceAtMemStick = Android_GetFreeSpaceByContentUri(pendingMemStickFolder_.ToString()); - } else { - freeSpaceAtMemStick = Android_GetFreeSpaceByFilePath(pendingMemStickFolder_.ToString()); - } - - leftColumn->Add(new TextView(pendingMemStickFolder_.ToVisualString(), ALIGN_LEFT, false)); - std::string freeSpaceText = "Free space: N/A"; - if (freeSpaceAtMemStick >= 0) { - freeSpaceText = StringFromFormat("free space: %lld MB", freeSpaceAtMemStick / (1024 * 1024)); - leftColumn->Add(new TextView(freeSpaceText, ALIGN_LEFT, false)); - } - } - - if (!g_Config.memStickDirectory.empty()) { - TextView *view = leftColumn->Add(new TextView(g_Config.memStickDirectory.ToVisualString(), ALIGN_LEFT, false)); - view->SetShadow(true); - } -#endif - leftColumn->Add(new Choice(iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnBrowse); leftColumn->Add(new TextView(iz->T("ChooseFolderDesc", "* Data will stay even if you uninstall PPSSPP.\n* Data can be shared with PPSSPP Gold\n* Easy USB access"), ALIGN_LEFT, false)); @@ -101,13 +135,8 @@ void MemStickScreen::CreateViews() { leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); - Choice *confirmButton = rightColumnItems->Add(new Choice(iz->T("Confirm"))); - confirmButton->OnClick.Handle(this, &MemStickScreen::OnConfirm); - confirmButton->SetEnabled(!pendingMemStickFolder_.empty()); - if (!initialSetup_) { - rightColumnItems->Add(new CheckBox(&moveData_, iz->T("Move Data"))); - rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnOK); + leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); } INFO_LOG(SYSTEM, "MemStickScreen: initialSetup=%d", (int)initialSetup_); @@ -115,6 +144,16 @@ void MemStickScreen::CreateViews() { UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { pendingMemStickFolder_ = Path(g_extFilesDir); + + if (initialSetup_) { + // There's not gonna be any files here in this case since it's a fresh install. + // Let's just accept it and move on. No need to move files either. + SwitchMemstickFolderTo(pendingMemStickFolder_); + TriggerFinish(DialogResult::DR_OK); + } else { + // Always ask for confirmation when called from the UI. Likely there's already some data. + screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder_, false)); + } return UI::EVENT_DONE; } @@ -133,51 +172,195 @@ void MemStickScreen::sendMessage(const char *message, const char *value) { std::string filename; filename = value; INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str()); + + // Browse finished. Let's pop up the confirmation dialog. pendingMemStickFolder_ = Path(filename); - RecreateViews(); + bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder_); + screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder_, initialSetup_)); } } } -UI::EventReturn MemStickScreen::OnConfirm(UI::EventParams ¶ms) { - auto sy = GetI18NCategory("System"); +void MemStickScreen::dialogFinished(const Screen *dialog, DialogResult result) { + if (result == DialogResult::DR_OK) { + INFO_LOG(SYSTEM, "Confirmation screen done - moving on."); + // There's a screen manager bug if we call TriggerFinish directly. + // Can't be bothered right now, so we pick this up in update(). + done_ = true; + } + // otherwise, we just keep going. +} - Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; - Path testWriteFile = pendingMemStickFolder_ / ".write_verify_file"; +void MemStickScreen::update() { + UIDialogScreenWithBackground::update(); + if (done_) { + TriggerFinish(DialogResult::DR_OK); + done_ = false; + } +} - // Doesn't already exist, create. - // Should this ever happen? - if (pendingMemStickFolder_.Type() == PathType::NATIVE) { - if (!File::Exists(pendingMemStickFolder_)) { - File::CreateFullPath(pendingMemStickFolder_); +static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vector &dirSuffixes, std::vector &fileSuffixes) { + std::vector files; + if (!File::GetFilesInDir(folder, &files)) { + return false; + } + + for (auto &file : files) { + if (file.isDirectory) { + dirSuffixes.push_back(root.PathTo(folder)); + ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes); + } else { + fileSuffixes.push_back(root.PathTo(file.fullName)); } - if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { - // settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + } + + return true; +} + +ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup) + : newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup) { + existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder); + if (initialSetup_) { + moveData_ = false; + } +} + +void ConfirmMemstickMoveScreen::CreateViews() { + using namespace UI; + auto di = GetI18NCategory("Dialog"); + auto sy = GetI18NCategory("System"); + auto iz = GetI18NCategory("MemStick"); + + root_ = new LinearLayout(ORIENT_HORIZONTAL); + + Path oldMemstickFolder = g_Config.memStickDirectory; + + Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f)); + ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + ViewGroup *rightColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + root_->Add(spacerColumn); + root_->Add(leftColumn); + root_->Add(rightColumn); + + int64_t freeSpaceNew; + int64_t freeSpaceOld; + free_disk_space(newMemstickFolder_, freeSpaceNew); + free_disk_space(oldMemstickFolder, freeSpaceOld); + + leftColumn->Add(new TextView(iz->T("New PSP Data Folder"), ALIGN_LEFT, false)); + leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false)); + std::string newFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceNew); + leftColumn->Add(new TextView(newFreeSpaceText, ALIGN_LEFT, false)); + if (existingFilesInNewFolder_) { + leftColumn->Add(new TextView(iz->T("Warning: Already contains data"), ALIGN_LEFT, false)); + } + if (!error_.empty()) { + leftColumn->Add(new TextView(error_, ALIGN_LEFT, false)); + } + + if (!oldMemstickFolder.empty()) { + std::string oldFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceOld); + rightColumn->Add(new TextView(iz->T("Old PSP Data Folder"), ALIGN_LEFT, false)); + rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false)); + rightColumn->Add(new TextView(oldFreeSpaceText, ALIGN_LEFT, false)); + } + + if (!initialSetup_) { + leftColumn->Add(new CheckBox(&moveData_, iz->T("Move Data"))); + } + + leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm); + leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); +} + +UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { + auto sy = GetI18NCategory("System"); + auto iz = GetI18NCategory("MemStick"); + + // Transfer all the files in /PSP from the original directory. + // Should probably be done on a background thread so we can show some UI. + // So we probably need another screen for this with a progress bar.. + // If the directory itself is called PSP, don't go below. + + if (moveData_) { + Path moveSrc = g_Config.memStickDirectory; + Path moveDest = newMemstickFolder_; + if (moveSrc.GetFilename() != "PSP") { + moveSrc = moveSrc / "PSP"; + } + if (moveDest.GetFilename() != "PSP") { + moveDest = moveDest / "PSP"; + File::CreateDir(moveDest); + } + + INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str()); + + // Search through recursively, listing the files to move and also summing their sizes. + std::vector fileSuffixesToMove; + std::vector directorySuffixesToCreate; + + // NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion. + if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) { + // TODO: Handle failure listing files. + error_ = "Failed to read old directory"; + INFO_LOG(SYSTEM, "%s", error_.c_str()); + return UI::EVENT_DONE; + } + + bool dryRun = true; // Useful for debugging. + + size_t moveFailures = 0; + + if (!moveSrc.empty()) { + // Better not interrupt the app while this is happening! + + // Create all the necessary directories. + for (auto &dirSuffix : directorySuffixesToCreate) { + Path dir = moveDest / dirSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); + } else { + if (!File::Exists(dir)) { + File::CreateDir(dir); + } + } + } + for (auto &fileSuffix : fileSuffixesToMove) { + Path from = moveSrc / fileSuffix; + Path to = moveDest / fileSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s'", from.c_str(), to.c_str()); + } else { + // Remove the "from" prefix from the path. + // We have to drop down to string operations for this. + if (!File::Move(from, to)) { + ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); + moveFailures++; + // Should probably just bail? + } + } + } + } + + if (moveFailures > 0) { + error_ = "Failed to move some files!"; + RecreateViews(); return UI::EVENT_DONE; } - File::Delete(testWriteFile); - } else { - // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse - // for folder, we can assume it exists and is writable, barring wacky race conditions like the user - // being connected by USB and deleting it. } - // This doesn't need the storage API - this path is accessible the normal way. - std::string str = pendingMemStickFolder_.ToString(); - if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { - ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", pendingMemStickFolder_.c_str(), memStickDirFile.c_str()); - // Not sure what to do if this file. - } + // Successful so far, switch the memstick folder. + SwitchMemstickFolderTo(newMemstickFolder_); + + // If the chosen folder already had a config, reload it! + g_Config.Load(); + - // Save so the settings, at least, are transferred. - g_Config.memStickDirectory = pendingMemStickFolder_; - g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); - g_Config.UpdateIniLocation(); if (g_Config.Save("MemstickPathChanged")) { TriggerFinish(DialogResult::DR_OK); } else { - error_ = sy->T("Failed to save config"); + error_ = iz->T("Failed to save config"); RecreateViews(); } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 7f7ccaafa2..1a5d12b63d 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -30,27 +30,43 @@ // Currently only useful for Android. class MemStickScreen : public UIDialogScreenWithBackground { public: - MemStickScreen(bool initialSetup) - : initialSetup_(initialSetup) {} - virtual ~MemStickScreen(); + MemStickScreen(bool initialSetup); + ~MemStickScreen() {} std::string tag() const override { return "game"; } void CreateViews() override; protected: void sendMessage(const char *message, const char *value) override; + void dialogFinished(const Screen *dialog, DialogResult result) override; + void update() override; private: // Event handlers UI::EventReturn OnBrowse(UI::EventParams &e); - UI::EventReturn OnConfirm(UI::EventParams ¶ms); UI::EventReturn OnUseInternalStorage(UI::EventParams ¶ms); + // TODO: probably not necessary to store here, we just forward to the confirmation dialog. Path pendingMemStickFolder_; SettingInfoMessage *settingInfo_ = nullptr; - std::string error_; - bool initialSetup_; + bool done_ = false; +}; + +class ConfirmMemstickMoveScreen : public UIDialogScreenWithBackground { +public: + ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup); + + void CreateViews() override; + +private: + UI::EventReturn OnConfirm(UI::EventParams ¶ms); + + Path newMemstickFolder_; + bool existingFilesInNewFolder_; bool moveData_ = true; + bool initialSetup_; + + std::string error_; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 8925f0485c..6a0d731c0b 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -322,7 +322,9 @@ static bool CheckFontIsUsable(const wchar_t *fontFace) { } #endif -static void PostLoadConfig() { +bool CreateDirectoriesAndroid(); + +void PostLoadConfig() { // On Windows, we deal with currentDirectory in InitSysDirectories(). #if !PPSSPP_PLATFORM(WINDOWS) if (g_Config.currentDirectory.empty()) { @@ -339,11 +341,14 @@ static void PostLoadConfig() { i18nrepo.LoadIni(g_Config.sLanguageIni); else i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath); + +#if PPSSPP_PLATFORM(ANDROID) + CreateDirectoriesAndroid(); +#endif } -static bool CreateDirectoriesAndroid() { - // On Android, create a PSP directory tree in the external_dir, - // to hopefully reduce confusion a bit. +bool CreateDirectoriesAndroid() { + // TODO: Really not sure why this code is Android-exclusive, except the ".nomedia" part. Path pspDir = g_Config.memStickDirectory; if (pspDir.GetFilename() != "PSP") { @@ -530,9 +535,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } else { ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str()); if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - // Ask the user to configure a memstick directory. - INFO_LOG(SYSTEM, "Asking the user."); - g_Config.memStickDirectory.clear(); + // Ask the user to configure a memstick directory. + INFO_LOG(SYSTEM, "Asking the user."); + g_Config.memStickDirectory.clear(); } } } @@ -785,7 +790,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch screenManager = new ScreenManager(); if (g_Config.memStickDirectory.empty()) { INFO_LOG(SYSTEM, "No memstick directory! Asking for one to be configured."); - screenManager->switchScreen(new MainScreen()); + screenManager->switchScreen(new LogoScreen(false)); screenManager->push(new MemStickScreen(true)); } else if (gotoGameSettings) { screenManager->switchScreen(new LogoScreen(true)); From f51b8116a08e8346a5aa1b3e80a76afd99459912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Jul 2021 00:54:41 +0200 Subject: [PATCH 04/15] Basic flow of setting up and moving the PSP directory works now. --- Common/File/FileUtil.cpp | 5 ++++- Common/File/Path.cpp | 23 ++++++++++++++--------- Common/File/Path.h | 2 +- UI/GameSettingsScreen.cpp | 6 ++++++ UI/GameSettingsScreen.h | 1 + UI/MemStickScreen.cpp | 20 ++++++++++++++++---- UI/MemStickScreen.h | 9 +++++++++ android/jni/AndroidContentURI.h | 12 ++++++++++-- unittest/UnitTest.cpp | 13 ++++++++++--- 9 files changed, 71 insertions(+), 20 deletions(-) diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 134d84f74b..dc8551767f 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -510,7 +510,10 @@ bool CreateFullPath(const Path &path) { Path root = path.GetRootVolume(); - std::string diff = root.PathTo(path); // always with forward slashes + std::string diff; + if (!root.ComputePathTo(path, diff)) { + return false; + } std::vector parts; SplitString(diff, '/', parts); diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index 86a43f20bd..2af4374937 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -299,27 +299,32 @@ bool Path::IsAbsolute() const { return false; } -std::string Path::PathTo(const Path &other) const { +bool Path::ComputePathTo(const Path &other, std::string &path) const { if (!other.StartsWith(*this)) { // Can't do this. Should return an error. - return std::string(); + return false; + } + + if (*this == other) { + // Equal, the path is empty. + path.clear(); + return true; } std::string diff; - if (type_ == PathType::CONTENT_URI) { AndroidContentURI a(path_); AndroidContentURI b(other.path_); if (a.RootPath() != b.RootPath()) { // No common root, can't do anything - return std::string(); + return false; } - diff = a.PathTo(b); + return a.ComputePathTo(b, path); } else if (path_ == "/") { - diff = other.path_.substr(1); + path = other.path_.substr(1); + return true; } else { - diff = other.path_.substr(path_.size() + 1); + path = other.path_.substr(path_.size() + 1); + return true; } - - return diff; } diff --git a/Common/File/Path.h b/Common/File/Path.h index afec18ac7e..4a0658e5f7 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -82,7 +82,7 @@ public: // For Android directory trees, navigates to the root of the tree. Path GetRootVolume() const; - std::string PathTo(const Path &child) const; + bool ComputePathTo(const Path &other, std::string &path) const; bool operator ==(const Path &other) const { return path_ == other.path_ && type_ == other.type_; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 71006d005b..18c8398060 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1257,6 +1257,12 @@ void GameSettingsScreen::sendMessage(const char *message, const char *value) { } } +void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult result) { + if (result == DialogResult::DR_OK) { + RecreateViews(); + } +} + void GameSettingsScreen::CallbackMemstickFolder(bool yes) { auto sy = GetI18NCategory("System"); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index ebc5d59f88..09127656ec 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -43,6 +43,7 @@ protected: void CallbackInflightFrames(bool yes); void CallbackMemstickFolder(bool yes); bool UseVerticalLayout() const; + void dialogFinished(const Screen *dialog, DialogResult result) override; private: void TriggerRestart(const char *why); diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 0e62fd644d..2696ed4004 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -207,10 +207,18 @@ static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vect for (auto &file : files) { if (file.isDirectory) { - dirSuffixes.push_back(root.PathTo(folder)); - ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes); + std::string dirSuffix; + if (root.ComputePathTo(file.fullName, dirSuffix)) { + dirSuffixes.push_back(dirSuffix); + ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes); + } else { + ERROR_LOG(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str()); + } } else { - fileSuffixes.push_back(root.PathTo(file.fullName)); + std::string fileSuffix; + if (root.ComputePathTo(file.fullName, fileSuffix)) { + fileSuffixes.push_back(fileSuffix); + } } } @@ -307,7 +315,7 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { return UI::EVENT_DONE; } - bool dryRun = true; // Useful for debugging. + bool dryRun = false; // Useful for debugging. size_t moveFailures = 0; @@ -320,11 +328,13 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { if (dryRun) { INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); } else { + INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str()); if (!File::Exists(dir)) { File::CreateDir(dir); } } } + for (auto &fileSuffix : fileSuffixesToMove) { Path from = moveSrc / fileSuffix; Path to = moveDest / fileSuffix; @@ -337,6 +347,8 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); moveFailures++; // Should probably just bail? + } else { + INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); } } } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 1a5d12b63d..84d6d4d84c 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -40,6 +40,15 @@ protected: void sendMessage(const char *message, const char *value) override; void dialogFinished(const Screen *dialog, DialogResult result) override; void update() override; + void render() override { + // Simple anti-flicker due to delayed finish. + if (!done_) { + // render as usual. + UIDialogScreenWithBackground::render(); + } else { + // no render. black frame insertion is better than flicker. + } + } private: // Event handlers diff --git a/android/jni/AndroidContentURI.h b/android/jni/AndroidContentURI.h index f9837a5ba4..92e77afb62 100644 --- a/android/jni/AndroidContentURI.h +++ b/android/jni/AndroidContentURI.h @@ -112,8 +112,16 @@ public: } // Only goes downwards in hierarchies. No ".." will ever be generated. - std::string PathTo(const AndroidContentURI &other) const { - return other.FilePath().substr(FilePath().size() + 1); + bool ComputePathTo(const AndroidContentURI &other, std::string &path) const { + size_t offset = FilePath().size() + 1; + std::string otherFilePath = other.FilePath(); + if (offset >= otherFilePath.size()) { + ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str()); + return false; + } + + path = other.FilePath().substr(FilePath().size() + 1); + return true; } std::string GetFileExtension() const { diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 7c399f0f9c..f2ef8cdb88 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -607,8 +607,14 @@ static bool TestPath() { EXPECT_EQ_STR(Path("C:\\Yo\\Lo").GetDirectory(), std::string("C:/Yo")); EXPECT_EQ_STR(Path("C:\\Yo\\Lo").GetFilename(), std::string("Lo")); - EXPECT_EQ_STR(Path("/a/b").PathTo(Path("/a/b/c/d/e")), std::string("c/d/e")); - EXPECT_EQ_STR(Path("/").PathTo(Path("/home/foo/bar")), std::string("home/foo/bar")); + std::string computedPath; + + EXPECT_TRUE(Path("/a/b").ComputePathTo(Path("/a/b/c/d/e"), computedPath)); + + EXPECT_EQ_STR(computedPath, std::string("c/d/e")); + + EXPECT_TRUE(Path("/").ComputePathTo(Path("/home/foo/bar"), computedPath)); + EXPECT_EQ_STR(computedPath, std::string("home/foo/bar")); return true; } @@ -639,7 +645,8 @@ static bool TestAndroidContentURI() { EXPECT_EQ_STR(fileURI.ToString(), std::string(directoryURIString)); - std::string diff = dirURI.PathTo(fileURI); + std::string diff; + EXPECT_TRUE(dirURI.ComputePathTo(fileURI, diff)); EXPECT_EQ_STR(diff, std::string("Tekken 6.iso")); return true; From 2d88ae511298c60f99579f76f6557ef35ea47d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Jul 2021 01:15:32 +0200 Subject: [PATCH 05/15] Delete some left-behind lines --- Core/HLE/sceIo.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index c856794110..0c4dcf20e1 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -558,13 +558,6 @@ static void __IoAsyncEndCallback(SceUID threadID, SceUID prevCallbackId) { } } -static DirectoryFileSystem *exdataSystem = nullptr; -#if defined(USING_WIN_UI) || defined(APPLE) -static DirectoryFileSystem *flash0System = nullptr; -#else -static VFSFileSystem *flash0System = nullptr; -#endif - static void __IoManagerThread() { SetCurrentThreadName("IO"); while (ioManagerThreadEnabled && coreState != CORE_BOOT_ERROR && coreState != CORE_RUNTIME_ERROR && coreState != CORE_POWERDOWN) { From 59088e0921e8dc0417ff62f813e5519417378f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Jul 2021 21:49:37 +0200 Subject: [PATCH 06/15] Android 28 was the wrong cutoff for oldschool permissions, 29 is correct. --- android/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 9c2477efa2..7ccdce7798 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -19,8 +19,8 @@ - - + + From 64dbd97731bfd1cf6ff51e924d7e3cf3018f59ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Jul 2021 22:11:07 +0200 Subject: [PATCH 07/15] Address feedback --- Common/Data/Text/Parsers.cpp | 17 +++++++++++++++++ Common/Data/Text/Parsers.h | 2 ++ Common/File/DiskFree.cpp | 4 +++- Core/Dialog/SavedataParam.cpp | 5 ++--- UI/MemStickScreen.cpp | 6 ++++-- UI/MemStickScreen.h | 4 +++- UI/NativeApp.cpp | 2 +- Windows/W32Util/Misc.cpp | 18 ------------------ Windows/W32Util/Misc.h | 2 -- 9 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Common/Data/Text/Parsers.cpp b/Common/Data/Text/Parsers.cpp index 427fb74316..b6ce78d958 100644 --- a/Common/Data/Text/Parsers.cpp +++ b/Common/Data/Text/Parsers.cpp @@ -6,6 +6,23 @@ #include "Common/StringUtils.h" +// Not strictly a parser... +void NiceSizeFormat(size_t size, char *out, size_t bufSize) { + const char *sizes[] = { "B","KB","MB","GB","TB","PB","EB" }; + int s = 0; + int frac = 0; + while (size >= 1024) { + s++; + frac = (int)size & 1023; + size /= 1024; + } + float f = (float)size + ((float)frac / 1024.0f); + if (s == 0) + snprintf(out, bufSize, "%d B", (int)size); + else + snprintf(out, bufSize, "%3.1f %s", f, sizes[s]); +} + bool Version::ParseVersionString(std::string str) { if (str.empty()) return false; diff --git a/Common/Data/Text/Parsers.h b/Common/Data/Text/Parsers.h index bfa111774c..35b3839cfb 100644 --- a/Common/Data/Text/Parsers.h +++ b/Common/Data/Text/Parsers.h @@ -70,3 +70,5 @@ static bool TryParse(const std::string &str, N *const output) { } else return false; } + +void NiceSizeFormat(size_t size, char *out, size_t bufSize); diff --git a/Common/File/DiskFree.cpp b/Common/File/DiskFree.cpp index f1412a35be..ea8e0f0d39 100644 --- a/Common/File/DiskFree.cpp +++ b/Common/File/DiskFree.cpp @@ -41,9 +41,11 @@ bool free_disk_space(const Path &path, int64_t &space) { int res = statvfs(path.c_str(), &diskstat); if (res == 0) { + // Not sure why we're excluding Android here anyway... #ifndef __ANDROID__ if (diskstat.f_flag & ST_RDONLY) { - space = -1; + // No space to write. + space = 0; return true; } #endif diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index 1e26486e2c..0558dacc60 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -1045,11 +1045,11 @@ int SavedataParam::BuildHash(unsigned char *output, return 0; } +// TODO: Merge with NiceSizeFormat? That one has a decimal though. std::string SavedataParam::GetSpaceText(u64 size, bool roundUp) { - static const char *suffixes[] = {"B", "KB", "MB", "GB"}; char text[50]; - + static const char *suffixes[] = {"B", "KB", "MB", "GB"}; for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) { if (size < 1024) @@ -1063,7 +1063,6 @@ std::string SavedataParam::GetSpaceText(u64 size, bool roundUp) size /= 1024; } } - snprintf(text, sizeof(text), "%llu TB", size); return std::string(text); } diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 2696ed4004..65a8a85b76 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -29,6 +29,7 @@ #include "Common/System/NativeApp.h" #include "Common/System/Display.h" #include "Common/Data/Text/I18n.h" +#include "Common/Data/Text/Parsers.h" #include "Common/File/AndroidStorage.h" #include "Common/File/FileUtil.h" @@ -91,8 +92,9 @@ static bool SwitchMemstickFolderTo(Path newMemstickFolder) { static std::string FormatSpaceString(int64_t space) { if (space >= 0) { - // TODO: Smarter display (MB, GB as appropriate). Don't we have one of these somewhere? - return StringFromFormat("%lld MB", space / (1024 * 1024)); + char buffer[50]; + NiceSizeFormat(space, buffer, sizeof(buffer)); + return buffer; } else { return "N/A"; } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 84d6d4d84c..6fb9df31d3 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -34,9 +34,10 @@ public: ~MemStickScreen() {} std::string tag() const override { return "game"; } - void CreateViews() override; protected: + void CreateViews() override; + void sendMessage(const char *message, const char *value) override; void dialogFinished(const Screen *dialog, DialogResult result) override; void update() override; @@ -67,6 +68,7 @@ class ConfirmMemstickMoveScreen : public UIDialogScreenWithBackground { public: ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup); +protected: void CreateViews() override; private: diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 6a0d731c0b..fd11d1ca5f 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -348,7 +348,7 @@ void PostLoadConfig() { } bool CreateDirectoriesAndroid() { - // TODO: Really not sure why this code is Android-exclusive, except the ".nomedia" part. + // TODO: We should probably simply use this as the shared function to create memstick directories. Path pspDir = g_Config.memStickDirectory; if (pspDir.GetFilename() != "PSP") { diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index d646302986..4f0ffb982f 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -53,24 +53,6 @@ namespace W32Util MoveWindow(hwnd, x, y, width, height, FALSE); } - void NiceSizeFormat(size_t size, char *out) - { - const char *sizes[] = {"B","KB","MB","GB","TB","PB","EB"}; - int s = 0; - int frac = 0; - while (size>=1024) - { - s++; - frac = (int)size & 1023; - size /= 1024; - } - float f = (float)size + ((float)frac / 1024.0f); - if (s==0) - sprintf(out, "%d B", (int)size); - else - sprintf(out, "%3.1f %s", f, sizes[s]); - } - BOOL CopyTextToClipboard(HWND hwnd, const char *text) { std::wstring wtext = ConvertUTF8ToWString(text); return CopyTextToClipboard(hwnd, wtext); diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index fa447de78d..0cddf63df9 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -6,8 +6,6 @@ namespace W32Util { void CenterWindow(HWND hwnd); - HBITMAP CreateBitmapFromARGB(HWND someHwnd, DWORD *image, int w, int h); - void NiceSizeFormat(size_t size, char *out); BOOL CopyTextToClipboard(HWND hwnd, const char *text); BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext); void MakeTopMost(HWND hwnd, bool topMost); From 1b7c2a36d8408081c4f657e3911420893fd1ac3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Jul 2021 22:15:34 +0200 Subject: [PATCH 08/15] UI message cleanup --- UI/MemStickScreen.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 65a8a85b76..1a7dc7ffaa 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -64,9 +64,7 @@ static bool SwitchMemstickFolderTo(Path newMemstickFolder) { File::CreateFullPath(newMemstickFolder); } if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { - // settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); - // TODO: Display an error! - return UI::EVENT_DONE; + return false; } File::Delete(testWriteFile); } else { @@ -150,8 +148,11 @@ UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { if (initialSetup_) { // There's not gonna be any files here in this case since it's a fresh install. // Let's just accept it and move on. No need to move files either. - SwitchMemstickFolderTo(pendingMemStickFolder_); - TriggerFinish(DialogResult::DR_OK); + if (SwitchMemstickFolderTo(pendingMemStickFolder_)) { + TriggerFinish(DialogResult::DR_OK); + } else { + // This can't really happen?? Not worth making an error message. + } } else { // Always ask for confirmation when called from the UI. Likely there's already some data. screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder_, false)); @@ -364,13 +365,15 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { } // Successful so far, switch the memstick folder. - SwitchMemstickFolderTo(newMemstickFolder_); + if (!SwitchMemstickFolderTo(newMemstickFolder_)) { + // TODO: More precise errors. + error_ = iz->T("That folder doesn't work as a memstick folder."); + return UI::EVENT_DONE; + } // If the chosen folder already had a config, reload it! g_Config.Load(); - - if (g_Config.Save("MemstickPathChanged")) { TriggerFinish(DialogResult::DR_OK); } else { From 80fc09a222e71f27953594882656c3286fefdd6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Jul 2021 22:22:58 +0200 Subject: [PATCH 09/15] Indentation fix --- android/AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 7ccdce7798..833ed5827b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -46,8 +46,7 @@ android:isGame="true" android:banner="@drawable/tv_banner" android:requestLegacyExternalStorage="true" - android:preserveLegacyExternalStorage="true" - > + android:preserveLegacyExternalStorage="true"> Date: Thu, 29 Jul 2021 23:03:43 +0200 Subject: [PATCH 10/15] When changing the PSP folder, move the files in a nice background process, showing progress. Not everything is properly refreshed after the move though - for example the main screen needs a refresh. --- UI/MemStickScreen.cpp | 210 ++++++++++++++++++++++++++++-------------- UI/MemStickScreen.h | 32 ++++++- 2 files changed, 173 insertions(+), 69 deletions(-) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 1a7dc7ffaa..5b51cbba9a 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -36,6 +36,8 @@ #include "Common/File/Path.h" #include "Common/File/DiskFree.h" +#include "Common/Thread/ThreadManager.h" + #include "Core/Util/GameManager.h" #include "Core/System.h" #include "Core/Config.h" @@ -236,6 +238,14 @@ ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(Path newMemstickFolder, boo } } +ConfirmMemstickMoveScreen::~ConfirmMemstickMoveScreen() { + if (moveDataTask_) { + INFO_LOG(SYSTEM, "Move Data task still running, blocking on it"); + moveDataTask_->BlockUntilReady(); + delete moveDataTask_; + } +} + void ConfirmMemstickMoveScreen::CreateViews() { using namespace UI; auto di = GetI18NCategory("Dialog"); @@ -276,14 +286,50 @@ void ConfirmMemstickMoveScreen::CreateViews() { rightColumn->Add(new TextView(oldFreeSpaceText, ALIGN_LEFT, false)); } - if (!initialSetup_) { - leftColumn->Add(new CheckBox(&moveData_, iz->T("Move Data"))); + if (moveDataTask_) { + progressView_ = leftColumn->Add(new TextView(progressReporter_.Get())); + } else { + progressView_ = nullptr; } - leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm); - leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); + if (!moveDataTask_) { + if (!initialSetup_) { + leftColumn->Add(new CheckBox(&moveData_, iz->T("Move Data"))); + } + + leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm); + leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); + } } +void ConfirmMemstickMoveScreen::update() { + UIDialogScreenWithBackground::update(); + + if (moveDataTask_) { + if (progressView_) { + progressView_->SetText(progressReporter_.Get()); + } + + bool *result = moveDataTask_->Poll(); + + if (result) { + if (*result) { + progressReporter_.Set("Done!"); + INFO_LOG(SYSTEM, "Move data task finished successfully!"); + // Succeeded! + FinishFolderMove(); + } else { + INFO_LOG(SYSTEM, "Move data task failed!"); + // What do we do here? We might be in the middle of a move... Bad. + RecreateViews(); + } + delete moveDataTask_; + moveDataTask_ = nullptr; + } + } +} + + UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { auto sy = GetI18NCategory("System"); auto iz = GetI18NCategory("MemStick"); @@ -294,81 +340,113 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { // If the directory itself is called PSP, don't go below. if (moveData_) { - Path moveSrc = g_Config.memStickDirectory; - Path moveDest = newMemstickFolder_; - if (moveSrc.GetFilename() != "PSP") { - moveSrc = moveSrc / "PSP"; - } - if (moveDest.GetFilename() != "PSP") { - moveDest = moveDest / "PSP"; - File::CreateDir(moveDest); - } + progressReporter_.Set("Starting move..."); - INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str()); - - // Search through recursively, listing the files to move and also summing their sizes. - std::vector fileSuffixesToMove; - std::vector directorySuffixesToCreate; - - // NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion. - if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) { - // TODO: Handle failure listing files. - error_ = "Failed to read old directory"; - INFO_LOG(SYSTEM, "%s", error_.c_str()); - return UI::EVENT_DONE; - } - - bool dryRun = false; // Useful for debugging. - - size_t moveFailures = 0; - - if (!moveSrc.empty()) { - // Better not interrupt the app while this is happening! - - // Create all the necessary directories. - for (auto &dirSuffix : directorySuffixesToCreate) { - Path dir = moveDest / dirSuffix; - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); - } else { - INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str()); - if (!File::Exists(dir)) { - File::CreateDir(dir); - } - } + moveDataTask_ = Promise::Spawn(&g_threadManager, [&]() -> bool * { + Path moveSrc = g_Config.memStickDirectory; + Path moveDest = newMemstickFolder_; + if (moveSrc.GetFilename() != "PSP") { + moveSrc = moveSrc / "PSP"; + } + if (moveDest.GetFilename() != "PSP") { + moveDest = moveDest / "PSP"; + File::CreateDir(moveDest); } - for (auto &fileSuffix : fileSuffixesToMove) { - Path from = moveSrc / fileSuffix; - Path to = moveDest / fileSuffix; - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s'", from.c_str(), to.c_str()); - } else { - // Remove the "from" prefix from the path. - // We have to drop down to string operations for this. - if (!File::Move(from, to)) { - ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); - moveFailures++; - // Should probably just bail? + INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str()); + + // Search through recursively, listing the files to move and also summing their sizes. + std::vector fileSuffixesToMove; + std::vector directorySuffixesToCreate; + + // NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion. + if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) { + // TODO: Handle failure listing files. + std::string error = "Failed to read old directory"; + INFO_LOG(SYSTEM, "%s", error.c_str()); + progressReporter_.Set(error); + return new bool(false); + } + + bool dryRun = false; // Useful for debugging. + + size_t moveFailures = 0; + + if (!moveSrc.empty()) { + // Better not interrupt the app while this is happening! + + // Create all the necessary directories. + for (auto &dirSuffix : directorySuffixesToCreate) { + Path dir = moveDest / dirSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); } else { - INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); + INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str()); + if (!File::Exists(dir)) { + File::CreateDir(dir); + } + } + } + + for (auto &fileSuffix : fileSuffixesToMove) { + progressReporter_.Set(fileSuffix); + + Path from = moveSrc / fileSuffix; + Path to = moveDest / fileSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s'", from.c_str(), to.c_str()); + } else { + // Remove the "from" prefix from the path. + // We have to drop down to string operations for this. + if (!File::Move(from, to)) { + ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); + moveFailures++; + // Should probably just bail? + } else { + INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); + } + } + } + + // Delete all the old, now hopefully empty, directories. + for (auto &dirSuffix : directorySuffixesToCreate) { + Path dir = moveSrc / dirSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have deleted dir '%s'", dir.c_str()); + } else { + INFO_LOG(SYSTEM, "Deleting dir '%s'", dir.c_str()); + if (!File::Exists(dir)) { + File::DeleteDir(dir); + } } } } - } - if (moveFailures > 0) { - error_ = "Failed to move some files!"; - RecreateViews(); - return UI::EVENT_DONE; - } + if (moveFailures > 0) { + std::string error = "Failed to move some files!"; + progressReporter_.Set(error); + return new bool(false); + } + + return new bool(true); + }, TaskType::IO_BLOCKING); + + RecreateViews(); + } else { + FinishFolderMove(); } + return UI::EVENT_DONE; +} + +void ConfirmMemstickMoveScreen::FinishFolderMove() { + auto iz = GetI18NCategory("MemStick"); + // Successful so far, switch the memstick folder. if (!SwitchMemstickFolderTo(newMemstickFolder_)) { // TODO: More precise errors. error_ = iz->T("That folder doesn't work as a memstick folder."); - return UI::EVENT_DONE; + return; } // If the chosen folder already had a config, reload it! @@ -380,6 +458,4 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { error_ = iz->T("Failed to save config"); RecreateViews(); } - - return UI::EVENT_DONE; } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 6fb9df31d3..7b9f843562 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -19,12 +19,15 @@ #include #include +#include #include "ppsspp_config.h" #include "Common/File/Path.h" -#include "UI/MiscScreens.h" #include "Common/UI/UIScreen.h" +#include "Common/Thread/Promise.h" + +#include "UI/MiscScreens.h" // MemStickScreen - let's you configure your memory stick directory. // Currently only useful for Android. @@ -64,14 +67,34 @@ private: bool done_ = false; }; +class ProgressReporter { +public: + void Set(std::string value) { + std::lock_guard guard(mutex_); + progress_ = value; + } + + std::string Get() { + std::lock_guard guard(mutex_); + return progress_; + } + +private: + std::string progress_; + std::mutex mutex_; +}; + class ConfirmMemstickMoveScreen : public UIDialogScreenWithBackground { public: ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup); - + ~ConfirmMemstickMoveScreen(); protected: + void update() override; void CreateViews() override; private: + void FinishFolderMove(); + UI::EventReturn OnConfirm(UI::EventParams ¶ms); Path newMemstickFolder_; @@ -79,5 +102,10 @@ private: bool moveData_ = true; bool initialSetup_; + ProgressReporter progressReporter_; + UI::TextView *progressView_ = nullptr; + + Promise *moveDataTask_ = nullptr; + std::string error_; }; From dbd3d8ca2a969da5e9119bb037b43e49b8c7e7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 29 Jul 2021 23:15:35 +0200 Subject: [PATCH 11/15] Handle an empty path in the game browser. --- Common/File/PathBrowser.cpp | 8 +++++++- UI/MainScreen.cpp | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index ab85e2422b..756f0f74b3 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -144,11 +144,14 @@ void PathBrowser::HandlePath() { std::unique_lock guard(pendingLock_); std::vector results; - Path lastPath; + Path lastPath("NONSENSE THAT WONT EQUAL A PATH"); while (!pendingStop_) { while (lastPath == pendingPath_ && !pendingCancel_) { pendingCond_.wait(guard); } + if (pendingStop_) { + break; + } lastPath = pendingPath_; bool success = false; if (lastPath.Type() == PathType::HTTP) { @@ -156,6 +159,9 @@ void PathBrowser::HandlePath() { results.clear(); success = LoadRemoteFileList(lastPath, &pendingCancel_, results); guard.lock(); + } else if (lastPath.empty()) { + results.clear(); + success = true; } else { guard.unlock(); results.clear(); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index bc07677ac5..4009dc7c42 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -816,7 +816,7 @@ void GameBrowser::Refresh() { } // Show a button to toggle pinning at the very end. - if (browseFlags_ & BrowseFlags::PIN) { + if ((browseFlags_ & BrowseFlags::PIN) && !path_.GetPath().empty()) { std::string caption = IsCurrentPathPinned() ? "-" : "+"; if (!*gridStyle_) { caption = IsCurrentPathPinned() ? mm->T("UnpinPath", "Unpin") : mm->T("PinPath", "Pin"); @@ -825,6 +825,10 @@ void GameBrowser::Refresh() { OnClick.Handle(this, &GameBrowser::PinToggleClick); } + if (path_.GetPath().empty()) { + Add(new TextView(mm->T("UseBrowseOrLoad", "Use Browse to choose a folder, or Load to choose a file."))); + } + if (!lastText_.empty() && gameButtons.empty()) { Add(new Spacer()); Add(new Choice(lastText_, new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT)))->OnClick.Handle(this, &GameBrowser::LastClick); From 93673cb56593ac5bab5754e8efab01f712197810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Aug 2021 22:51:22 +0200 Subject: [PATCH 12/15] Correct the cutoff for scoped storage. Android 9,10 (API 28,29) shouldn't use it. --- android/jni/app-android.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index fb2723f9c1..f7baea793d 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -97,19 +97,6 @@ struct JNIEnv {}; bool useCPUThread = true; -// We turn this on now that when we target Android 11+. -// Along with adding: -// android:preserveLegacyExternalStorage="true" -// To the already requested: -// android:requestLegacyExternalStorage="true" -// -// This will cause Android 11+ to still behave like Android 10 until the app -// is manually uninstalled. We can detect this state with -// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see -// that scoped storage enforcement is disabled in this case. - -static const bool useScopedStorageIfRequired = true; - enum class EmuThreadState { DISABLED, START_REQUESTED, @@ -466,6 +453,8 @@ bool System_GetPropertyBool(SystemProperty prop) { return androidVersion >= 19; // when ACTION_OPEN_DOCUMENT was added case SYSPROP_HAS_FOLDER_BROWSER: // Uses OPEN_DOCUMENT_TREE to let you select a folder. + // Doesn't actually mean it's usable though, in many early versions of Android + // this dialog is complete garbage and only lets you select subfolders of the Downloads folder. return androidVersion >= 21; // when ACTION_OPEN_DOCUMENT_TREE was added case SYSPROP_APP_GOLD: #ifdef GOLD @@ -476,7 +465,17 @@ bool System_GetPropertyBool(SystemProperty prop) { case SYSPROP_CAN_JIT: return true; case SYSPROP_ANDROID_SCOPED_STORAGE: - if (useScopedStorageIfRequired && androidVersion >= 28) { + // We turn this on for Android 30+ (11) now that when we target Android 11+. + // Along with adding: + // android:preserveLegacyExternalStorage="true" + // To the already requested: + // android:requestLegacyExternalStorage="true" + // + // This will cause Android 11+ to still behave like Android 10 until the app + // is manually uninstalled. We can detect this state with + // Android_IsExternalStoragePreservedLegacy(), but most of the app will just see + // that scoped storage enforcement is disabled in this case. + if (androidVersion >= 30) { // Here we do a check to see if we ended up in the preserveLegacyExternalStorage path. // That won't last if the user uninstalls/reinstalls though, but would preserve the user // experience for simple upgrades so maybe let's support it. From a0a5f7a67a27c563593ca1e9c522f482bbfd782a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Aug 2021 23:21:28 +0200 Subject: [PATCH 13/15] Minor improvements on Android 9/10. Let the user reset to root of storage. This includes handling weird "raw:/" content URIs. These I haven't seen on newer versions. --- Common/File/AndroidStorage.cpp | 4 +++- Common/File/AndroidStorage.h | 7 +++--- Common/File/Path.cpp | 40 ++++++++++++++++++++++++---------- UI/GameSettingsScreen.cpp | 6 ++--- UI/MemStickScreen.cpp | 39 +++++++++++++++++++++++++++------ UI/MemStickScreen.h | 3 +-- android/jni/app-android.cpp | 4 +++- 7 files changed, 74 insertions(+), 29 deletions(-) diff --git a/Common/File/AndroidStorage.cpp b/Common/File/AndroidStorage.cpp index d8d6468b91..78d457aabf 100644 --- a/Common/File/AndroidStorage.cpp +++ b/Common/File/AndroidStorage.cpp @@ -279,7 +279,9 @@ const char *Android_ErrorToString(StorageError error) { #else -// This string should never appear except on Android. +// These strings should never appear except on Android. +// Very hacky. std::string g_extFilesDir = "(IF YOU SEE THIS THERE'S A BUG)"; +std::string g_externalDir = "(IF YOU SEE THIS THERE'S A BUG (2))"; #endif diff --git a/Common/File/AndroidStorage.h b/Common/File/AndroidStorage.h index 131c0125d2..2a890d33b4 100644 --- a/Common/File/AndroidStorage.h +++ b/Common/File/AndroidStorage.h @@ -30,12 +30,13 @@ inline StorageError StorageErrorFromInt(int ival) { } } +extern std::string g_extFilesDir; +extern std::string g_externalDir; + #if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__) #include -extern std::string g_extFilesDir; - void Android_StorageSetNativeActivity(jobject nativeActivity); bool Android_IsContentUri(const std::string &uri); @@ -59,8 +60,6 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj); #else -extern std::string g_extFilesDir; - // Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere. inline bool Android_IsContentUri(const std::string &uri) { return false; } diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index 2af4374937..3ffd64549d 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -10,16 +10,6 @@ #include "android/jni/AndroidContentURI.h" Path::Path(const std::string &str) { - if (str.empty()) { - type_ = PathType::UNDEFINED; - } else if (startsWith(str, "http://") || startsWith(str, "https://")) { - type_ = PathType::HTTP; - } else if (Android_IsContentUri(str)) { - type_ = PathType::CONTENT_URI; - } else { - type_ = PathType::NATIVE; - } - Init(str); } @@ -31,7 +21,35 @@ Path::Path(const std::wstring &str) { #endif void Path::Init(const std::string &str) { - path_ = str; + if (str.empty()) { + type_ = PathType::UNDEFINED; + path_.clear(); + } else if (startsWith(str, "http://") || startsWith(str, "https://")) { + type_ = PathType::HTTP; + path_ = str; + } else if (Android_IsContentUri(str)) { + // Content URIs on non scoped-storage (and possibly other cases?) can contain + // "raw:/" URIs inside. This happens when picking the Download folder using the folder browser + // on Android 9. + // Here's an example: + // content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fp/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fp + // + // Since this is a legacy use case, I think it's safe enough to just detect this + // and flip it to a NATIVE url and hope for the best. + AndroidContentURI uri(str); + if (startsWith(uri.FilePath(), "raw:/")) { + INFO_LOG(SYSTEM, "Raw path detected: %s", uri.FilePath().c_str()); + path_ = uri.FilePath().substr(4); + type_ = PathType::NATIVE; + } else { + // A normal content URI path. + type_ = PathType::CONTENT_URI; + path_ = str; + } + } else { + type_ = PathType::NATIVE; + path_ = str; + } #if PPSSPP_PLATFORM(WINDOWS) // Flip all the slashes around. We flip them back on ToWString(). diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 18c8398060..6b1e97e368 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -903,9 +903,6 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new ItemHeader(sy->T("PSP Memory Stick"))); - systemSettings->Add(new CheckBox(&g_Config.bMemStickInserted, sy->T("Memory Stick inserted"))); - systemSettings->Add(new PopupSliderChoice(&g_Config.iMemStickSizeGB, 1, 32, sy->T("Change Memory Stick Size", "Memory Stick Size (GB)"), screenManager(), "GB")); - #if PPSSPP_PLATFORM(ANDROID) memstickDisplay_ = g_Config.memStickDirectory.ToVisualString(); auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Change Memory Stick folder", "Memory Stick folder"), (const char *)nullptr)); @@ -955,6 +952,9 @@ void GameSettingsScreen::CreateViews() { } } #endif + systemSettings->Add(new CheckBox(&g_Config.bMemStickInserted, sy->T("Memory Stick inserted"))); + systemSettings->Add(new PopupSliderChoice(&g_Config.iMemStickSizeGB, 1, 32, sy->T("Change Memory Stick Size", "Memory Stick Size (GB)"), screenManager(), "GB")); + systemSettings->Add(new ItemHeader(sy->T("General"))); #if PPSSPP_PLATFORM(ANDROID) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 5b51cbba9a..f2eae807b8 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -102,7 +102,6 @@ static std::string FormatSpaceString(int64_t space) { MemStickScreen::MemStickScreen(bool initialSetup) : initialSetup_(initialSetup) { - pendingMemStickFolder_ = g_Config.memStickDirectory; } void MemStickScreen::CreateViews() { @@ -129,6 +128,14 @@ void MemStickScreen::CreateViews() { leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose PSP data storage (Memory Stick)"), ALIGN_LEFT, false)); + // For legacy Android systems, so you can switch back to the old ways if you move to SD or something. + // TODO: Gonna need a scroll view. +#if PPSSPP_PLATFORM(ANDROID) + if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + leftColumn->Add(new Choice(iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnUseStorageRoot); + } +#endif + leftColumn->Add(new Choice(iz->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnBrowse); leftColumn->Add(new TextView(iz->T("ChooseFolderDesc", "* Data will stay even if you uninstall PPSSPP.\n* Data can be shared with PPSSPP Gold\n* Easy USB access"), ALIGN_LEFT, false)); @@ -145,19 +152,37 @@ void MemStickScreen::CreateViews() { } UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) { - pendingMemStickFolder_ = Path(g_extFilesDir); + Path pendingMemStickFolder = Path(g_extFilesDir); if (initialSetup_) { // There's not gonna be any files here in this case since it's a fresh install. // Let's just accept it and move on. No need to move files either. - if (SwitchMemstickFolderTo(pendingMemStickFolder_)) { + if (SwitchMemstickFolderTo(pendingMemStickFolder)) { TriggerFinish(DialogResult::DR_OK); } else { // This can't really happen?? Not worth making an error message. } } else { // Always ask for confirmation when called from the UI. Likely there's already some data. - screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder_, false)); + screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, false)); + } + return UI::EVENT_DONE; +} + +UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) { + Path pendingMemStickFolder = Path(g_externalDir); + + if (initialSetup_) { + // There's not gonna be any files here in this case since it's a fresh install. + // Let's just accept it and move on. No need to move files either. + if (SwitchMemstickFolderTo(pendingMemStickFolder)) { + TriggerFinish(DialogResult::DR_OK); + } else { + // This can't really happen?? Not worth making an error message. + } + } else { + // Always ask for confirmation when called from the UI. Likely there's already some data. + screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, false)); } return UI::EVENT_DONE; } @@ -179,9 +204,9 @@ void MemStickScreen::sendMessage(const char *message, const char *value) { INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str()); // Browse finished. Let's pop up the confirmation dialog. - pendingMemStickFolder_ = Path(filename); - bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder_); - screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder_, initialSetup_)); + Path pendingMemStickFolder = Path(filename); + bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder); + screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, initialSetup_)); } } } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 7b9f843562..d058d3c490 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -58,9 +58,8 @@ private: // Event handlers UI::EventReturn OnBrowse(UI::EventParams &e); UI::EventReturn OnUseInternalStorage(UI::EventParams ¶ms); + UI::EventReturn OnUseStorageRoot(UI::EventParams ¶ms); - // TODO: probably not necessary to store here, we just forward to the confirmation dialog. - Path pendingMemStickFolder_; SettingInfoMessage *settingInfo_ = nullptr; bool initialSetup_; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index f7baea793d..795ec23f4e 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -128,7 +128,8 @@ std::string langRegion; std::string mogaVersion; std::string boardName; -std::string g_extFilesDir; +std::string g_externalDir; // Original external dir (root of Android storage). +std::string g_extFilesDir; // App private external dir. std::vector g_additionalStorageDirs; @@ -637,6 +638,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs); std::string externalFilesDir = GetJavaString(env, jexternalFilesDir); + g_externalDir = externalStorageDir; g_extFilesDir = externalFilesDir; if (!additionalStorageDirsString.empty()) { From ff5b6ca3dae32e4c8e3297eb897c16225a98c47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Aug 2021 21:53:08 +0200 Subject: [PATCH 14/15] A bunch of small scoped storage UX improvements. --- UI/DevScreens.cpp | 8 +++++--- UI/MemStickScreen.cpp | 46 +++++++++++++++++++++++++++++++++++-------- UI/MemStickScreen.h | 1 + 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 81f17d589f..2d18c69315 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -589,10 +589,12 @@ void SystemInfoScreen::CreateViews() { #if PPSSPP_PLATFORM(ANDROID) storage->Add(new InfoItem("ExtFilesDir", g_extFilesDir)); - if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - storage->Add(new InfoItem("Scoped Storage", di->T("Yes"))); + bool scoped = System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); + storage->Add(new InfoItem("Scoped Storage Enabled", scoped ? di->T("Yes") : di->T("No"))); + if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) { + // This flag is only relevant on Android API 30+. + storage->Add(new InfoItem("IsStoragePreservedLegacy", Android_IsExternalStoragePreservedLegacy() ? di->T("Yes") : di->T("No"))); } - storage->Add(new InfoItem("IsStoragePreservedLegacy", Android_IsExternalStoragePreservedLegacy() ? di->T("Yes") : di->T("No"))); #endif ViewGroup *buildConfigScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index f2eae807b8..41d7c63aa2 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -48,8 +48,8 @@ static bool FolderSeemsToBeUsed(Path newMemstickFolder) { // Inspect the potential new folder. - if (File::Exists(newMemstickFolder / "PSP") || File::Exists(newMemstickFolder / "SYSTEM")) { - // Does seem likely. We could add more critera like checking for actual savegames or something. + if (File::Exists(newMemstickFolder / "PSP/SAVEDATA") || File::Exists(newMemstickFolder / "SAVEDATA")) { + // Does seem likely. We could add more criteria like checking for actual savegames or something. return true; } else { return false; @@ -115,17 +115,25 @@ void MemStickScreen::CreateViews() { root_ = new LinearLayout(ORIENT_HORIZONTAL); Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f)); - ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + ScrollView *leftColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + + ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL); ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); root_->Add(spacerColumn); - root_->Add(leftColumn); + root_->Add(leftColumnScroll); root_->Add(rightColumnItems); + leftColumnScroll->Add(leftColumn); + if (initialSetup_) { leftColumn->Add(new TextView(iz->T("Welcome to PPSSPP!"), ALIGN_LEFT, false)); leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); } + if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + leftColumn->Add(new TextView(iz->T("ScopedStorageWarning", "WARNING: BETA ANDROID SCOPED STORAGE SUPPORT\nMAY EAT YOUR DATA"), ALIGN_LEFT, false)); + } + leftColumn->Add(new TextView(iz->T("MemoryStickDescription", "Choose PSP data storage (Memory Stick)"), ALIGN_LEFT, false)); // For legacy Android systems, so you can switch back to the old ways if you move to SD or something. @@ -133,6 +141,7 @@ void MemStickScreen::CreateViews() { #if PPSSPP_PLATFORM(ANDROID) if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { leftColumn->Add(new Choice(iz->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnUseStorageRoot); + leftColumn->Add(new TextView(iz->T("ChooseFolderDesc", "* Data will stay even if you uninstall PPSSPP.\n* Data can be shared with PPSSPP Gold\n* Easy USB access"), ALIGN_LEFT, false)); } #endif @@ -145,7 +154,7 @@ void MemStickScreen::CreateViews() { leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); if (!initialSetup_) { - leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); + rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); } INFO_LOG(SYSTEM, "MemStickScreen: initialSetup=%d", (int)initialSetup_); @@ -188,7 +197,6 @@ UI::EventReturn MemStickScreen::OnUseStorageRoot(UI::EventParams ¶ms) { } UI::EventReturn MemStickScreen::OnBrowse(UI::EventParams ¶ms) { - auto sy = GetI18NCategory("System"); System_SendMessage("browse_folder", ""); return UI::EVENT_DONE; } @@ -205,6 +213,12 @@ void MemStickScreen::sendMessage(const char *message, const char *value) { // Browse finished. Let's pop up the confirmation dialog. Path pendingMemStickFolder = Path(filename); + + if (pendingMemStickFolder == g_Config.memStickDirectory) { + auto iz = GetI18NCategory("MemStick"); + SystemToast(iz->T("That's the folder being used!")); + } + bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder); screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, initialSetup_)); } @@ -294,11 +308,17 @@ void ConfirmMemstickMoveScreen::CreateViews() { free_disk_space(oldMemstickFolder, freeSpaceOld); leftColumn->Add(new TextView(iz->T("New PSP Data Folder"), ALIGN_LEFT, false)); + if (!initialSetup_) { + leftColumn->Add(new TextView(iz->T("PPSSPP will restart after the change."), ALIGN_LEFT, false)); + } leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false)); std::string newFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceNew); leftColumn->Add(new TextView(newFreeSpaceText, ALIGN_LEFT, false)); if (existingFilesInNewFolder_) { - leftColumn->Add(new TextView(iz->T("Warning: Already contains data"), ALIGN_LEFT, false)); + leftColumn->Add(new TextView(iz->T("Already contains data."), ALIGN_LEFT, false)); + if (!moveData_) { + leftColumn->Add(new TextView(iz->T("No data will be changed."), ALIGN_LEFT, false)); + } } if (!error_.empty()) { leftColumn->Add(new TextView(error_, ALIGN_LEFT, false)); @@ -319,7 +339,7 @@ void ConfirmMemstickMoveScreen::CreateViews() { if (!moveDataTask_) { if (!initialSetup_) { - leftColumn->Add(new CheckBox(&moveData_, iz->T("Move Data"))); + leftColumn->Add(new CheckBox(&moveData_, iz->T("Move Data")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnMoveDataClick); } leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm); @@ -327,6 +347,11 @@ void ConfirmMemstickMoveScreen::CreateViews() { } } +UI::EventReturn ConfirmMemstickMoveScreen::OnMoveDataClick(UI::EventParams ¶ms) { + RecreateViews(); + return UI::EVENT_DONE; +} + void ConfirmMemstickMoveScreen::update() { UIDialogScreenWithBackground::update(); @@ -477,6 +502,11 @@ void ConfirmMemstickMoveScreen::FinishFolderMove() { // If the chosen folder already had a config, reload it! g_Config.Load(); + if (!initialSetup_) { + // We restart the app here, to get the new settings. + System_SendMessage("graphics_restart", ""); + } + if (g_Config.Save("MemstickPathChanged")) { TriggerFinish(DialogResult::DR_OK); } else { diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index d058d3c490..2c248103f0 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -92,6 +92,7 @@ protected: void CreateViews() override; private: + UI::EventReturn OnMoveDataClick(UI::EventParams ¶ms); void FinishFolderMove(); UI::EventReturn OnConfirm(UI::EventParams ¶ms); From 61198164075ce346d2f2f5dffd23051902eaecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Aug 2021 23:37:35 +0200 Subject: [PATCH 15/15] Buildfix, bugfix --- UI/MemStickScreen.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 41d7c63aa2..318195738e 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -216,7 +216,10 @@ void MemStickScreen::sendMessage(const char *message, const char *value) { if (pendingMemStickFolder == g_Config.memStickDirectory) { auto iz = GetI18NCategory("MemStick"); +#if PPSSPP_PLATFORM(ANDROID) SystemToast(iz->T("That's the folder being used!")); +#endif + return; } bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder);