mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 17:49:29 +01:00
Merge pull request #14619 from hrydgard/turn-on-scoped-storage
Enable scoped storage enforcement on Android 11+.
This commit is contained in:
35 files changed
+726
-198
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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 <jni.h>
|
||||
|
||||
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; }
|
||||
|
||||
@@ -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)) {
|
||||
@@ -41,8 +41,10 @@ bool free_disk_space(const Path &path, uint64_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) {
|
||||
// No space to write.
|
||||
space = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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<std::string> parts;
|
||||
SplitString(diff, '/', parts);
|
||||
|
||||
+43
-21
@@ -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().
|
||||
@@ -236,7 +254,6 @@ bool Path::CanNavigateUp() const {
|
||||
if (type_ == PathType::CONTENT_URI) {
|
||||
return AndroidContentURI(path_).CanNavigateUp();
|
||||
}
|
||||
|
||||
if (path_ == "/" || path_ == "") {
|
||||
return false;
|
||||
}
|
||||
@@ -300,27 +317,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;
|
||||
}
|
||||
+1
-1
@@ -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_;
|
||||
|
||||
@@ -144,11 +144,14 @@ void PathBrowser::HandlePath() {
|
||||
|
||||
std::unique_lock<std::mutex> guard(pendingLock_);
|
||||
std::vector<File::FileInfo> 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();
|
||||
|
||||
@@ -35,6 +35,10 @@ public:
|
||||
}
|
||||
std::string GetFriendlyPath() const;
|
||||
|
||||
bool empty() const {
|
||||
return path_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
void HandlePath();
|
||||
void ResetPending();
|
||||
|
||||
@@ -89,3 +89,5 @@ void NativeSetMixer(void* mixer);
|
||||
// Main thread.
|
||||
void NativeShutdownGraphics();
|
||||
void NativeShutdown();
|
||||
|
||||
void PostLoadConfig();
|
||||
@@ -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_)
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -925,9 +925,9 @@ std::vector<PSPFileInfo> 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+6
-6
@@ -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",
|
||||
@@ -592,8 +589,11 @@ 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")));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+7
-2
@@ -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);
|
||||
@@ -978,7 +982,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();
|
||||
|
||||
+430
-66
@@ -25,23 +25,84 @@
|
||||
#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/Data/Text/Parsers.h"
|
||||
|
||||
#include "Common/File/AndroidStorage.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#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"
|
||||
|
||||
#include "UI/MemStickScreen.h"
|
||||
#include "UI/MainScreen.h"
|
||||
#include "UI/MiscScreens.h"
|
||||
|
||||
#include "Core/Config.h"
|
||||
static bool FolderSeemsToBeUsed(Path newMemstickFolder) {
|
||||
// Inspect the potential new folder.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
MemStickScreen::~MemStickScreen() { }
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
char buffer[50];
|
||||
NiceSizeFormat(space, buffer, sizeof(buffer));
|
||||
return buffer;
|
||||
} else {
|
||||
return "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
MemStickScreen::MemStickScreen(bool initialSetup)
|
||||
: initialSetup_(initialSetup) {
|
||||
}
|
||||
|
||||
void MemStickScreen::CreateViews() {
|
||||
using namespace UI;
|
||||
@@ -51,49 +112,91 @@ void MemStickScreen::CreateViews() {
|
||||
|
||||
Margins actionMenuMargins(15, 15, 15, 0);
|
||||
|
||||
root_ = new AnchorLayout();
|
||||
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
||||
|
||||
ViewGroup *columns = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT));
|
||||
root_->Add(columns);
|
||||
Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f));
|
||||
ScrollView *leftColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
|
||||
|
||||
ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0));
|
||||
ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL);
|
||||
ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
|
||||
columns->Add(leftColumn);
|
||||
columns->Add(rightColumnItems);
|
||||
root_->Add(spacerColumn);
|
||||
root_->Add(leftColumnScroll);
|
||||
root_->Add(rightColumnItems);
|
||||
|
||||
Path path = g_Config.memStickDirectory;
|
||||
int64_t freeSpaceAtMemStick = -1;
|
||||
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.
|
||||
// TODO: Gonna need a scroll view.
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (Android_IsContentUri(path.ToString())) {
|
||||
freeSpaceAtMemStick = Android_GetFreeSpaceByContentUri(path.ToString());
|
||||
} else {
|
||||
freeSpaceAtMemStick = Android_GetFreeSpaceByFilePath(path.ToString());
|
||||
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
|
||||
|
||||
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));
|
||||
if (!initialSetup_) {
|
||||
rightColumnItems->Add(new Choice(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
|
||||
}
|
||||
|
||||
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<UIScreen>(this, &UIScreen::OnOK);
|
||||
UI::EventReturn MemStickScreen::OnUseInternalStorage(UI::EventParams ¶ms) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
UI::EventReturn MemStickScreen::OnBrowse(UI::EventParams ¶ms) {
|
||||
auto sy = GetI18NCategory("System");
|
||||
System_SendMessage("browse_folder", "");
|
||||
return UI::EVENT_DONE;
|
||||
}
|
||||
@@ -107,49 +210,310 @@ void MemStickScreen::sendMessage(const char *message, const char *value) {
|
||||
std::string filename;
|
||||
filename = value;
|
||||
INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str());
|
||||
pendingMemStickFolder_ = Path(filename);
|
||||
CallbackMemStickFolder(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemStickScreen::CallbackMemStickFolder(bool yes) {
|
||||
auto sy = GetI18NCategory("System");
|
||||
// Browse finished. Let's pop up the confirmation dialog.
|
||||
Path pendingMemStickFolder = Path(filename);
|
||||
|
||||
if (yes) {
|
||||
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);
|
||||
if (pendingMemStickFolder == g_Config.memStickDirectory) {
|
||||
auto iz = GetI18NCategory("MemStick");
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
SystemToast(iz->T("That's the folder being used!"));
|
||||
#endif
|
||||
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.
|
||||
|
||||
bool existingFiles = FolderSeemsToBeUsed(pendingMemStickFolder);
|
||||
screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, initialSetup_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
}
|
||||
|
||||
void MemStickScreen::update() {
|
||||
UIDialogScreenWithBackground::update();
|
||||
if (done_) {
|
||||
TriggerFinish(DialogResult::DR_OK);
|
||||
done_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ListFileSuffixesRecursively(const Path &root, Path folder, std::vector<std::string> &dirSuffixes, std::vector<std::string> &fileSuffixes) {
|
||||
std::vector<File::FileInfo> files;
|
||||
if (!File::GetFilesInDir(folder, &files)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &file : files) {
|
||||
if (file.isDirectory) {
|
||||
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 {
|
||||
std::string fileSuffix;
|
||||
if (root.ComputePathTo(file.fullName, fileSuffix)) {
|
||||
fileSuffixes.push_back(fileSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup)
|
||||
: newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup) {
|
||||
existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder);
|
||||
if (initialSetup_) {
|
||||
moveData_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
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));
|
||||
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("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));
|
||||
}
|
||||
|
||||
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 (moveDataTask_) {
|
||||
progressView_ = leftColumn->Add(new TextView(progressReporter_.Get()));
|
||||
} else {
|
||||
progressView_ = nullptr;
|
||||
}
|
||||
|
||||
if (!moveDataTask_) {
|
||||
if (!initialSetup_) {
|
||||
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);
|
||||
leftColumn->Add(new Choice(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
|
||||
}
|
||||
}
|
||||
|
||||
UI::EventReturn ConfirmMemstickMoveScreen::OnMoveDataClick(UI::EventParams ¶ms) {
|
||||
RecreateViews();
|
||||
return UI::EVENT_DONE;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// 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_) {
|
||||
progressReporter_.Set("Starting move...");
|
||||
|
||||
moveDataTask_ = Promise<bool>::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);
|
||||
}
|
||||
|
||||
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<std::string> fileSuffixesToMove;
|
||||
std::vector<std::string> 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, "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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 (!initialSetup_) {
|
||||
// We restart the app here, to get the new settings.
|
||||
System_SendMessage("graphics_restart", "");
|
||||
}
|
||||
|
||||
g_Config.Save("MemstickPathChanged");
|
||||
screenManager()->RecreateAllViews();
|
||||
if (g_Config.Save("MemstickPathChanged")) {
|
||||
TriggerFinish(DialogResult::DR_OK);
|
||||
} else {
|
||||
error_ = iz->T("Failed to save config");
|
||||
RecreateViews();
|
||||
}
|
||||
}
|
||||
+69
-6
@@ -19,30 +19,93 @@
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
#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.
|
||||
class MemStickScreen : public UIDialogScreenWithBackground {
|
||||
public:
|
||||
MemStickScreen() {}
|
||||
virtual ~MemStickScreen();
|
||||
MemStickScreen(bool initialSetup);
|
||||
~MemStickScreen() {}
|
||||
|
||||
std::string tag() const override { return "game"; }
|
||||
|
||||
protected:
|
||||
void CreateViews() override;
|
||||
void CallbackMemStickFolder(bool yes);
|
||||
|
||||
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
|
||||
UI::EventReturn OnBrowse(UI::EventParams &e);
|
||||
Path pendingMemStickFolder_;
|
||||
SettingInfoMessage *settingInfo_;
|
||||
UI::EventReturn OnUseInternalStorage(UI::EventParams ¶ms);
|
||||
UI::EventReturn OnUseStorageRoot(UI::EventParams ¶ms);
|
||||
|
||||
SettingInfoMessage *settingInfo_ = nullptr;
|
||||
|
||||
bool initialSetup_;
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
class ProgressReporter {
|
||||
public:
|
||||
void Set(std::string value) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
progress_ = value;
|
||||
}
|
||||
|
||||
std::string Get() {
|
||||
std::lock_guard<std::mutex> 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:
|
||||
UI::EventReturn OnMoveDataClick(UI::EventParams ¶ms);
|
||||
void FinishFolderMove();
|
||||
|
||||
UI::EventReturn OnConfirm(UI::EventParams ¶ms);
|
||||
|
||||
Path newMemstickFolder_;
|
||||
bool existingFilesInNewFolder_;
|
||||
bool moveData_ = true;
|
||||
bool initialSetup_;
|
||||
|
||||
ProgressReporter progressReporter_;
|
||||
UI::TextView *progressView_ = nullptr;
|
||||
|
||||
Promise<bool> *moveDataTask_ = nullptr;
|
||||
|
||||
std::string error_;
|
||||
};
|
||||
+31
-24
@@ -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"
|
||||
@@ -321,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,12 +342,13 @@ static void PostLoadConfig() {
|
||||
else
|
||||
i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath);
|
||||
|
||||
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
|
||||
#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: We should probably simply use this as the shared function to create memstick directories.
|
||||
|
||||
Path pspDir = g_Config.memStickDirectory;
|
||||
if (pspDir.GetFilename() != "PSP") {
|
||||
@@ -436,7 +440,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 +453,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 +510,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 +535,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 +577,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 +591,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 +786,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 LogoScreen(false));
|
||||
screenManager->push(new MemStickScreen(true));
|
||||
} else if (gotoGameSettings) {
|
||||
screenManager->switchScreen(new LogoScreen(true));
|
||||
} else if (gotoTouchScreenTest) {
|
||||
screenManager->switchScreen(new MainScreen());
|
||||
@@ -832,7 +838,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 +1482,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.
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="archos.permission.FULLSCREEN.FULL" />
|
||||
@@ -45,7 +45,8 @@
|
||||
android:logo="@drawable/ic_banner"
|
||||
android:isGame="true"
|
||||
android:banner="@drawable/tv_banner"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:preserveLegacyExternalStorage="true">
|
||||
<meta-data android:name="android.max_aspect" android:value="2.4" />
|
||||
<activity
|
||||
android:name=".PpssppActivity"
|
||||
|
||||
@@ -44,8 +44,8 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.0'
|
||||
defaultConfig {
|
||||
applicationId 'org.ppsspp.ppsspp'
|
||||
if (androidGitVersion.name() != "unknown" && androidGitVersion.code() >= 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -97,9 +97,6 @@ struct JNIEnv {};
|
||||
|
||||
bool useCPUThread = true;
|
||||
|
||||
// We'll turn this on when we target Android 12.
|
||||
bool useScopedStorageIfRequired = false;
|
||||
|
||||
enum class EmuThreadState {
|
||||
DISABLED,
|
||||
START_REQUESTED,
|
||||
@@ -131,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<std::string> g_additionalStorageDirs;
|
||||
|
||||
@@ -433,7 +431,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:
|
||||
@@ -448,6 +454,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
|
||||
@@ -458,8 +466,24 @@ bool System_GetPropertyBool(SystemProperty prop) {
|
||||
case SYSPROP_CAN_JIT:
|
||||
return true;
|
||||
case SYSPROP_ANDROID_SCOPED_STORAGE:
|
||||
if (useScopedStorageIfRequired && androidVersion >= 28)
|
||||
return true;
|
||||
// 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.
|
||||
return !Android_IsExternalStoragePreservedLegacy();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -614,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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+10
-3
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user