diff --git a/CMakeLists.txt b/CMakeLists.txt index fa196f6d23..0445b74431 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1156,6 +1156,7 @@ list(APPEND NativeAppSource UI/Store.cpp UI/CwCheatScreen.cpp UI/InstallZipScreen.cpp + UI/MemStickScreen.cpp UI/ProfilerDraw.cpp UI/TextureUtil.cpp UI/ComboKeyMappingScreen.cpp diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 2536d4bdf7..bc252ebeaf 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -24,6 +24,8 @@ #include "ppsspp_config.h" +#include "android/jni/app-android.h" + #ifdef __MINGW32__ #include #ifndef _POSIX_THREAD_SAFE_FUNCTIONS @@ -257,7 +259,9 @@ bool IsDirectory(const Path &filename) { WIN32_FILE_ATTRIBUTE_DATA data{}; if (!GetFileAttributesEx(copy.c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) { auto err = GetLastError(); - WARN_LOG(COMMON, "GetFileAttributes failed on %s: %08x %s", fn.c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str()); + if (err != ERROR_FILE_NOT_FOUND) { + WARN_LOG(COMMON, "GetFileAttributes failed on %s: %08x %s", fn.c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str()); + } return false; } DWORD result = data.dwFileAttributes; diff --git a/Common/File/FileUtil.h b/Common/File/FileUtil.h index 1933f5893c..e74cd50557 100644 --- a/Common/File/FileUtil.h +++ b/Common/File/FileUtil.h @@ -27,6 +27,8 @@ #include "Common/Common.h" #include "Common/File/Path.h" +// Some functions here support Android content URIs. These are marked as such. + #ifdef _MSC_VER inline struct tm* localtime_r(const time_t *clock, struct tm *result) { if (localtime_s(result, clock) == 0) diff --git a/Core/FileSystems/AndroidStorageFileSystem.cpp b/Core/FileSystems/AndroidStorageFileSystem.cpp index 2c7878e0c3..bf3571e603 100644 --- a/Core/FileSystems/AndroidStorageFileSystem.cpp +++ b/Core/FileSystems/AndroidStorageFileSystem.cpp @@ -121,8 +121,12 @@ bool AndroidDirectoryFileHandle::Open(const std::string &basePath, std::string & success = false; } - // Seek to end if append mode. - Seek(0, FILEMOVE_END); + // TODO: Experiment with fstat to see if we can extract more info. + + // Seek to end to simulate append mode, if requested. + if (access & FILEACCESS_APPEND) { + Seek(0, FILEMOVE_END); + } // Try to detect reads/writes to PSP/GAME to avoid them in replays. if (basePath.find("/PSP/GAME/") != std::string::npos) { @@ -439,10 +443,11 @@ std::vector AndroidStorageFileSystem::GetDirListing(std::string pat bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles; for (auto &info : fileInfo) { PSPFileInfo entry; - if (info.isDirectory) + if (info.isDirectory) { entry.type = FILETYPE_DIRECTORY; - else + } else { entry.type = FILETYPE_NORMAL; + } entry.access = info.isWritable ? 0777 : 0666; entry.name = info.name; if (Flags() & FileSystemFlags::SIMULATE_FAT32) { @@ -466,8 +471,9 @@ std::vector AndroidStorageFileSystem::GetDirListing(std::string pat localtime_r((time_t*)&atime, &entry.atime); localtime_r((time_t*)&ctime, &entry.ctime); localtime_r((time_t*)&mtime, &entry.mtime); - if (!hideFile && (!listingRoot || (strcmp(info.name.c_str(), "..") && strcmp(info.name.c_str(), ".")))) + if (!hideFile && (!listingRoot || (strcmp(info.name.c_str(), "..") && strcmp(info.name.c_str(), ".")))) { myVector.push_back(entry); + } } return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs()); diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index 90bd14b1e7..6cc202e229 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -285,8 +285,7 @@ int VirtualDiscFileSystem::getFileListIndex(std::string &fileName) #endif } - FileType type = File::IsDirectory(fullName) ? FILETYPE_DIRECTORY : FILETYPE_NORMAL; - if (type == FILETYPE_DIRECTORY) + if (File::IsDirectory(fullName)) return -1; FileListEntry entry = {""}; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 393e33cd9f..6880eaca95 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -51,6 +51,7 @@ #include "UI/TiltEventProcessor.h" #include "UI/ComboKeyMappingScreen.h" #include "UI/GPUDriverTestScreen.h" +#include "UI/MemStickScreen.h" #include "Common/File/FileUtil.h" #include "Common/OSVersion.h" @@ -931,6 +932,7 @@ void GameSettingsScreen::CreateViews() { #if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Use system native keyboard"))); #endif + #if PPSSPP_PLATFORM(ANDROID) memstickDisplay_ = g_Config.memStickDirectory.ToVisualString(); auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Change Memory Stick folder"), (const char *)nullptr)); @@ -1098,40 +1100,12 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) { return UI::EVENT_DONE; } -#if PPSSPP_PLATFORM(ANDROID) - UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { - auto sy = GetI18NCategory("System"); - System_InputBoxGetString(sy->T("Memory Stick Folder"), g_Config.memStickDirectory.ToString(), [&](bool result, const std::string &value) { - auto sy = GetI18NCategory("System"); - auto di = GetI18NCategory("Dialog"); - - if (result) { - std::string newPath = value; - size_t pos = newPath.find_last_not_of("/"); - // Gotta have at least something but a /, and also needs to start with a /. - if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') { - settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); - return; - } - if (pos != newPath.size() - 1) { - newPath = newPath.substr(0, pos + 1); - } - - pendingMemstickFolder_ = newPath; - std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?"); - if (!File::Exists(Path(newPath))) { - promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?"); - } - // Add the path for clarity and proper confirmation. - promptMessage += "\n\n" + newPath + "/"; - screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1))); - } - }); + screenManager()->push(new MemStickScreen()); return UI::EVENT_DONE; } -#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) { const Path &PPSSPPpath = File::GetExeDirectory(); @@ -2006,52 +1980,3 @@ void HostnameSelectScreen::OnCompleted(DialogResult result) { if (result == DR_OK) *value_ = StripSpaces(addrView_->GetText()); } - -SettingInfoMessage::SettingInfoMessage(int align, UI::AnchorLayoutParams *lp) - : UI::LinearLayout(UI::ORIENT_HORIZONTAL, lp) { - using namespace UI; - SetSpacing(0.0f); - Add(new UI::Spacer(10.0f)); - text_ = Add(new UI::TextView("", align, false, new LinearLayoutParams(1.0, Margins(0, 10)))); - Add(new UI::Spacer(10.0f)); -} - -void SettingInfoMessage::Show(const std::string &text, UI::View *refView) { - if (refView) { - Bounds b = refView->GetBounds(); - const UI::AnchorLayoutParams *lp = GetLayoutParams()->As(); - if (b.y >= cutOffY_) { - ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, 80.0f, lp->right, lp->bottom, lp->center)); - } else { - ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->center)); - } - } - text_->SetText(text); - timeShown_ = time_now_d(); -} - -void SettingInfoMessage::Draw(UIContext &dc) { - static const double FADE_TIME = 1.0; - static const float MAX_ALPHA = 0.9f; - - // Let's show longer messages for more time (guesstimate at reading speed.) - // Note: this will give multibyte characters more time, but they often have shorter words anyway. - double timeToShow = std::max(1.5, text_->GetText().size() * 0.05); - - double sinceShow = time_now_d() - timeShown_; - float alpha = MAX_ALPHA; - if (timeShown_ == 0.0 || sinceShow > timeToShow + FADE_TIME) { - alpha = 0.0f; - } else if (sinceShow > timeToShow) { - alpha = MAX_ALPHA - MAX_ALPHA * (float)((sinceShow - timeToShow) / FADE_TIME); - } - - if (alpha >= 0.1f) { - UI::Style style = dc.theme->popupTitle; - style.background.color = colorAlpha(style.background.color, alpha - 0.1f); - dc.FillRect(style.background, bounds_); - } - - text_->SetTextColor(whiteAlpha(alpha)); - ViewGroup::Draw(dc); -} diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 934e472417..bc32e4b619 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -24,8 +24,6 @@ #include "Common/UI/UIScreen.h" #include "UI/MiscScreens.h" -class SettingInfoMessage; - // Per-game settings screen - enables you to configure graphic options, control options, etc // per game. class GameSettingsScreen : public UIDialogScreenWithGameBackground { @@ -108,9 +106,8 @@ private: UI::EventReturn OnMicDeviceChange(UI::EventParams& e); UI::EventReturn OnAudioDevice(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); -#if PPSSPP_PLATFORM(ANDROID) UI::EventReturn OnChangeMemStickDir(UI::EventParams &e); -#elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) UI::EventReturn OnSavePathMydoc(UI::EventParams &e); UI::EventReturn OnSavePathOther(UI::EventParams &e); #endif @@ -141,23 +138,6 @@ private: std::string pendingMemstickFolder_; }; -class SettingInfoMessage : public UI::LinearLayout { -public: - SettingInfoMessage(int align, UI::AnchorLayoutParams *lp); - - void SetBottomCutoff(float y) { - cutOffY_ = y; - } - void Show(const std::string &text, UI::View *refView = nullptr); - - void Draw(UIContext &dc); - -private: - UI::TextView *text_ = nullptr; - double timeShown_ = 0.0; - float cutOffY_; -}; - class DeveloperToolsScreen : public UIDialogScreenWithBackground { public: DeveloperToolsScreen() {} diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 901a5fb09a..485613e4eb 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -948,3 +948,52 @@ void CreditsScreen::render() { dc.Flush(); } + +SettingInfoMessage::SettingInfoMessage(int align, UI::AnchorLayoutParams *lp) + : UI::LinearLayout(UI::ORIENT_HORIZONTAL, lp) { + using namespace UI; + SetSpacing(0.0f); + Add(new UI::Spacer(10.0f)); + text_ = Add(new UI::TextView("", align, false, new LinearLayoutParams(1.0, Margins(0, 10)))); + Add(new UI::Spacer(10.0f)); +} + +void SettingInfoMessage::Show(const std::string &text, UI::View *refView) { + if (refView) { + Bounds b = refView->GetBounds(); + const UI::AnchorLayoutParams *lp = GetLayoutParams()->As(); + if (b.y >= cutOffY_) { + ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, 80.0f, lp->right, lp->bottom, lp->center)); + } else { + ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->center)); + } + } + text_->SetText(text); + timeShown_ = time_now_d(); +} + +void SettingInfoMessage::Draw(UIContext &dc) { + static const double FADE_TIME = 1.0; + static const float MAX_ALPHA = 0.9f; + + // Let's show longer messages for more time (guesstimate at reading speed.) + // Note: this will give multibyte characters more time, but they often have shorter words anyway. + double timeToShow = std::max(1.5, text_->GetText().size() * 0.05); + + double sinceShow = time_now_d() - timeShown_; + float alpha = MAX_ALPHA; + if (timeShown_ == 0.0 || sinceShow > timeToShow + FADE_TIME) { + alpha = 0.0f; + } else if (sinceShow > timeToShow) { + alpha = MAX_ALPHA - MAX_ALPHA * (float)((sinceShow - timeToShow) / FADE_TIME); + } + + if (alpha >= 0.1f) { + UI::Style style = dc.theme->popupTitle; + style.background.color = colorAlpha(style.background.color, alpha - 0.1f); + dc.FillRect(style.background, bounds_); + } + + text_->SetTextColor(whiteAlpha(alpha)); + ViewGroup::Draw(dc); +} diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index 4ceee02c2f..d1fc2bc6a7 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -164,3 +164,20 @@ private: double startTime_ = 0.0; }; + +class SettingInfoMessage : public UI::LinearLayout { +public: + SettingInfoMessage(int align, UI::AnchorLayoutParams *lp); + + void SetBottomCutoff(float y) { + cutOffY_ = y; + } + void Show(const std::string &text, UI::View *refView = nullptr); + + void Draw(UIContext &dc); + +private: + UI::TextView *text_ = nullptr; + double timeShown_ = 0.0; + float cutOffY_; +}; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 5eca2ba1f7..151667f04a 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -511,10 +511,12 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch Path memstickPath(memstickDir); if (!memstickPath.empty() && File::Exists(memstickPath)) { g_Config.memStickDirectory = memstickPath; + INFO_LOG(SYSTEM, "Memstick Directory from memstick_dir.txt: %s", g_Config.memStickDirectory.c_str()); } else { - ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str()); + ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str()); } } + #elif PPSSPP_PLATFORM(IOS) g_Config.defaultCurrentDirectory = g_Config.internalDataDirectory; g_Config.memStickDirectory = Path(user_data_path); diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 8c621c2f4e..b56a983acd 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -51,6 +51,7 @@ + @@ -85,6 +86,7 @@ + @@ -394,4 +396,4 @@ - + \ No newline at end of file diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index eb282757cf..2c63641535 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -78,6 +78,9 @@ Screens + + Screens + @@ -157,6 +160,9 @@ Screens + + Screens + diff --git a/android/jni/AndroidContentURI.h b/android/jni/AndroidContentURI.h index 8c81a9fd02..2cce0d1d9f 100644 --- a/android/jni/AndroidContentURI.h +++ b/android/jni/AndroidContentURI.h @@ -9,9 +9,12 @@ // content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO // content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO +// This file compiles on all platforms, to reduce the need for ifdefs. + // I am not 100% sure it's OK to rely on the internal format of file content URIs. // On the other hand, I'm sure tons of apps would break if these changed, so I think we can -// consider them pretty stable. +// consider them pretty stable. Additionally, the official Document library just manipulates the URIs +// in similar ways... class AndroidStorageContentURI { private: std::string provider; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index f0338cf0e4..e7587d61b8 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -177,6 +177,8 @@ static jmethodID contentUriCreateFile; static jmethodID contentUriCreateDirectory; static jmethodID contentUriRemoveFile; static jmethodID contentUriGetFileInfo; +static jmethodID contentUriGetFreeStorageSpace; +static jmethodID filePathGetFreeStorageSpace; static jobject nativeActivity; static volatile bool exitRenderLoop; @@ -342,6 +344,21 @@ std::vector Android_ListContentUri(const std::string &path) { return items; } +int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { + auto env = getEnv(); + + jstring param = env->NewStringUTF(uri.c_str()); + return env->CallLongMethod(nativeActivity, contentUriGetFreeStorageSpace, param); +} + +int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { + auto env = getEnv(); + + jstring param = env->NewStringUTF(filePath.c_str()); + return env->CallLongMethod(nativeActivity, filePathGetFreeStorageSpace, param); +} + + class ContentURIFileLoader : public ProxiedFileLoader { public: ContentURIFileLoader(const Path &filename) @@ -627,6 +644,10 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv * _dbg_assert_(contentUriRemoveFile); contentUriGetFileInfo = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFileInfo", "(Ljava/lang/String;)Ljava/lang/String;"); _dbg_assert_(contentUriGetFileInfo); + contentUriGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFreeStorageSpace", "(Ljava/lang/String;)J"); + _dbg_assert_(contentUriGetFreeStorageSpace); + filePathGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "filePathGetFreeStorageSpace", "(Ljava/lang/String;)J"); + _dbg_assert_(filePathGetFreeStorageSpace); } extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) { diff --git a/android/jni/app-android.h b/android/jni/app-android.h index fec7c229e3..b46331b197 100644 --- a/android/jni/app-android.h +++ b/android/jni/app-android.h @@ -4,10 +4,19 @@ #include #include +#include #include "Common/LogManager.h" #include "Common/File/DirListing.h" +// To emphasize that Android storage mode strings are different, let's just use +// an enum. +enum class Android_OpenContentUriMode { + READ = 0, // "r" + READ_WRITE = 1, // "rw" + READ_WRITE_TRUNCATE = 2, // "rwt" +}; + #if PPSSPP_PLATFORM(ANDROID) #include @@ -25,21 +34,30 @@ extern std::string g_extFilesDir; // Called from PathBrowser for example. bool Android_IsContentUri(const std::string &uri); - -// To emphasize that Android storage mode strings are different, let's just use -// an enum. -enum class Android_OpenContentUriMode { - READ = 0, // "r" - READ_WRITE = 1, // "rw" - READ_WRITE_TRUNCATE = 2, // "rwt" -}; - int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode); bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName); bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName); bool Android_RemoveFile(const std::string &fileUri); bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info); +int64_t Android_GetFreeSpaceByContentUri(const std::string &uri); +int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath); std::vector Android_ListContentUri(const std::string &uri); +#else + +// Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere. + +inline bool Android_IsContentUri(const std::string &uri) { return false; } +inline int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode) { return -1; } +inline bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return false; } +inline bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return false; } +inline bool Android_RemoveFile(const std::string &fileUri) { return false; } +inline bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info) { return false; } +inline int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { return -1; } +inline int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { return -1; } +inline std::vector Android_ListContentUri(const std::string &uri) { + return std::vector(); +} + #endif diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 190eb9132e..4a953be2bc 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -8,8 +8,11 @@ import android.os.Bundle; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.util.Log; +import android.os.storage.StorageManager; import androidx.documentfile.provider.DocumentFile; import java.util.ArrayList; +import java.util.UUID; +import java.io.File; public class PpssppActivity extends NativeActivity { private static final String TAG = "PpssppActivity"; @@ -223,4 +226,41 @@ public class PpssppActivity extends NativeActivity { return null; } } + + // The example in Android documentation uses this.getFilesDir for path. + // There's also a way to beg the OS for more space, which might clear caches, but + // let's just not bother with that for now. + public long contentUriGetFreeStorageSpace(String uriString) { + try { + StorageManager storageManager = getApplicationContext().getSystemService(StorageManager.class); + + // In 29 and later, we can directly get the UUID for the storage volume + // through the URI. + UUID volumeUUID; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Uri uri = Uri.parse(uriString); + volumeUUID = UUID.fromString(storageManager.getStorageVolume(uri).getUuid()); + } else { + volumeUUID = storageManager.getUuidForPath(this.getFilesDir()); + } + long availableBytes = storageManager.getAllocatableBytes(volumeUUID); + return availableBytes; + } catch (Exception e) { + Log.e(TAG, "Exception checking free space: " + e.toString()); + return -1; + } + } + + public long filePathGetFreeStorageSpace(String filePath) { + try { + StorageManager storageManager = getApplicationContext().getSystemService(StorageManager.class); + File file = new File(filePath); + UUID volumeUUID = storageManager.getUuidForPath(file); + long availableBytes = storageManager.getAllocatableBytes(volumeUUID); + return availableBytes; + } catch (Exception e) { + Log.e(TAG, "Exception checking free space: " + e.toString()); + return -1; + } + } }