Merge pull request #14619 from hrydgard/turn-on-scoped-storage

Enable scoped storage enforcement on Android 11+.
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2021-08-06 18:51:28 +02:00
commit 7a2b4fa5fa
35 files changed
+726 -198

No files matched your search

+17
View File
@@ -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;
+2
View File
@@ -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);
+3 -1
View File
@@ -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
+3 -4
View File
@@ -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; }
+3 -1
View File
@@ -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;
}
+2 -1
View File
@@ -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);
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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_;
+7 -1
View File
@@ -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();
+4
View File
@@ -35,6 +35,10 @@ public:
}
std::string GetFriendlyPath() const;
bool empty() const {
return path_.empty();
}
private:
void HandlePath();
void ResetPending();
+2
View File
@@ -89,3 +89,5 @@ void NativeSetMixer(void* mixer);
// Main thread.
void NativeShutdownGraphics();
void NativeShutdown();
void PostLoadConfig();
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+2 -3
View File
@@ -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);
}
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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
+4 -2
View File
@@ -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;
}
-7
View File
@@ -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
View File
@@ -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
+10 -4
View File
@@ -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");
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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 &params) {
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 &params) {
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 &params) {
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 &params) {
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 &params) {
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
View File
@@ -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 &params);
UI::EventReturn OnUseStorageRoot(UI::EventParams &params);
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 &params);
void FinishFolderMove();
UI::EventReturn OnConfirm(UI::EventParams &params);
Path newMemstickFolder_;
bool existingFilesInNewFolder_;
bool moveData_ = true;
bool initialSetup_;
ProgressReporter progressReporter_;
UI::TextView *progressView_ = nullptr;
Promise<bool> *moveDataTask_ = nullptr;
std::string error_;
};
+31 -24
View File
@@ -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.
}
-18
View File
@@ -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);
-2
View File
@@ -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);
+4 -3
View File
@@ -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"
+3 -3
View File
@@ -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
+10 -2
View File
@@ -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 {
+32 -7
View File
@@ -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
View File
@@ -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;