From 793e79945fe81a02331906e6cfb8cd6065881f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Jul 2021 19:50:36 +0200 Subject: [PATCH] Enable scoped storage enforcement on Android 11+. This has a number of UX issues and bugs we need to work through, but at least games are playable, things mostly work. Upgrades are handled smoothly by keeping existing storage access until you uninstall. After a reinstall, you'll need to re-select your old PSP directory manually in settings :( --- UI/DevScreens.cpp | 6 ++-- UI/MainScreen.cpp | 3 +- android/AndroidManifest.xml | 8 +++-- android/build.gradle | 6 ++-- android/jni/app-android.cpp | 34 ++++++++++++++++--- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 1 + 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 735ae47928..81f17d589f 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -23,6 +23,7 @@ #include "Common/System/NativeApp.h" #include "Common/System/System.h" #include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/File/AndroidStorage.h" #include "Common/Data/Text/I18n.h" #include "Common/Net/HTTPClient.h" #include "Common/UI/Context.h" @@ -59,12 +60,8 @@ int GetD3DCompilerVersion(); #endif -#if PPSSPP_PLATFORM(ANDROID) - #include "android/jni/app-android.h" -#endif - static const char *logLevelList[] = { "Notice", "Error", @@ -595,6 +592,7 @@ void SystemInfoScreen::CreateViews() { if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { storage->Add(new InfoItem("Scoped Storage", di->T("Yes"))); } + storage->Add(new InfoItem("IsStoragePreservedLegacy", Android_IsExternalStoragePreservedLegacy() ? di->T("Yes") : di->T("No"))); #endif ViewGroup *buildConfigScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 6f7872289a..bc07677ac5 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -978,7 +978,8 @@ void MainScreen::CreateViews() { tabHolder_->SetClip(true); bool showRecent = g_Config.iMaxRecent > 0; - bool hasStorageAccess = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; + bool hasStorageAccess = !System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS) || + System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; bool storageIsTemporary = IsTempPath(GetSysDirectory(DIRECTORY_SAVEDATA)) && !confirmedTemporary_; if (showRecent && !hasStorageAccess) { showRecent = !g_Config.recentIsos.empty(); diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 002a779cc4..9c2477efa2 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -19,8 +19,8 @@ - - + + @@ -45,7 +45,9 @@ android:logo="@drawable/ic_banner" android:isGame="true" android:banner="@drawable/tv_banner" - android:requestLegacyExternalStorage="true"> + android:requestLegacyExternalStorage="true" + android:preserveLegacyExternalStorage="true" + > = 14000000) { @@ -61,7 +61,7 @@ android { new File("versioncode.txt").write(androidGitVersion.code().toString()) minSdkVersion 9 - targetSdkVersion 29 + targetSdkVersion 30 if (project.hasProperty("ANDROID_VERSION_CODE") && project.hasProperty("ANDROID_VERSION_NAME")) { versionCode ANDROID_VERSION_CODE versionName ANDROID_VERSION_NAME diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 0cfe1f4e19..fb2723f9c1 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -97,8 +97,18 @@ struct JNIEnv {}; bool useCPUThread = true; -// We'll turn this on when we target Android 12. -bool useScopedStorageIfRequired = false; +// We turn this on now that when we target Android 11+. +// Along with adding: +// android:preserveLegacyExternalStorage="true" +// To the already requested: +// android:requestLegacyExternalStorage="true" +// +// This will cause Android 11+ to still behave like Android 10 until the app +// is manually uninstalled. We can detect this state with +// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see +// that scoped storage enforcement is disabled in this case. + +static const bool useScopedStorageIfRequired = true; enum class EmuThreadState { DISABLED, @@ -433,7 +443,15 @@ float System_GetPropertyFloat(SystemProperty prop) { bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_SUPPORTS_PERMISSIONS: - return androidVersion >= 23; // 6.0 Marshmallow introduced run time permissions. + if (androidVersion < 23) { + // 6.0 Marshmallow introduced run time permissions. + return false; + } else { + // It gets a bit complicated here. If scoped storage enforcement is on, + // we also don't need to request permissions. We'll have the access we request + // on a per-folder basis. + return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); + } case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE: return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature. case SYSPROP_HAS_ADDITIONAL_STORAGE: @@ -458,8 +476,14 @@ bool System_GetPropertyBool(SystemProperty prop) { case SYSPROP_CAN_JIT: return true; case SYSPROP_ANDROID_SCOPED_STORAGE: - if (useScopedStorageIfRequired && androidVersion >= 28) - return true; + if (useScopedStorageIfRequired && androidVersion >= 28) { + // Here we do a check to see if we ended up in the preserveLegacyExternalStorage path. + // That won't last if the user uninstalls/reinstalls though, but would preserve the user + // experience for simple upgrades so maybe let's support it. + return !Android_IsExternalStoragePreservedLegacy(); + } else { + return false; + } default: return false; } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 761cc156ab..f7786ef707 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.util.Log;