Skip to content

Commit

Permalink
Use hacky methods to retrieve SD card directory if available.
Browse files Browse the repository at this point in the history
This will disappear in future Android versions but can be useful for
older devices.

Fixes (or at least tries to) #10199, at least for some devices.

Might help #13827 ?

Tested on Pocophone F1.
  • Loading branch information
hrydgard committed Jan 6, 2021
1 parent 5929c78 commit 1c753e4
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 26 deletions.
11 changes: 8 additions & 3 deletions Common/System/System.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ void System_SendMessage(const char *command, const char *parameter);
PermissionStatus System_GetPermissionStatus(SystemPermission permission);
void System_AskForPermission(SystemPermission permission);

std::vector<std::string> System_GetExternalStorageDirs();

// This will get muddy with multi-screen support :/ But this will always be the type of the main device.
enum SystemDeviceType {
DEVICE_TYPE_MOBILE = 0, // phones and pads
Expand All @@ -55,14 +57,16 @@ enum SystemProperty {
SYSPROP_CLIPBOARD_TEXT,
SYSPROP_GPUDRIVER_VERSION,

// Separate SD cards or similar.
// Need hacky solutions to get at this.
SYSPROP_HAS_ADDITIONAL_STORAGE,
SYSPROP_ADDITIONAL_STORAGE_DIRS,

SYSPROP_HAS_FILE_BROWSER,
SYSPROP_HAS_FOLDER_BROWSER,
SYSPROP_HAS_IMAGE_BROWSER,
SYSPROP_HAS_BACK_BUTTON,

// Legacy on Android 29+.
SYSPROP_HAS_EXTERNAL_STORAGE,

// Available as Int:
SYSPROP_SYSTEMVERSION,
SYSPROP_DISPLAY_XRES,
Expand Down Expand Up @@ -98,6 +102,7 @@ enum SystemProperty {
};

std::string System_GetProperty(SystemProperty prop);
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop);
int System_GetPropertyInt(SystemProperty prop);
float System_GetPropertyFloat(SystemProperty prop);
bool System_GetPropertyBool(SystemProperty prop);
Expand Down
7 changes: 7 additions & 0 deletions Qt/QtMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ std::string System_GetProperty(SystemProperty prop) {
}
}

std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
switch (prop) {
default:
return std::vector<std::string>();
}
}

int System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
#if defined(SDL)
Expand Down
7 changes: 7 additions & 0 deletions SDL/SDLMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ std::string System_GetProperty(SystemProperty prop) {
}
}

std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
switch (prop) {
default:
return std::vector<std::string>();
}
}

int System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
case SYSPROP_AUDIO_SAMPLE_RATE:
Expand Down
15 changes: 12 additions & 3 deletions UI/MainScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,17 @@ UI::EventReturn GameBrowser::BrowseClick(UI::EventParams &e) {
}

UI::EventReturn GameBrowser::StorageClick(UI::EventParams &e) {
// TODO: Get the SD card directory on Android.
SetPath("");
std::vector<std::string> storageDirs = System_GetPropertyStringVec(SYSPROP_ADDITIONAL_STORAGE_DIRS);
if (storageDirs.empty()) {
// Shouldn't happen - this button shouldn't be clickable.
return UI::EVENT_DONE;
}
if (storageDirs.size() == 1) {
SetPath(storageDirs[0]);
} else {
// TODO: We should popup a dialog letting the user choose one.
SetPath(storageDirs[0]);
}
return UI::EVENT_DONE;
}

Expand Down Expand Up @@ -672,7 +681,7 @@ void GameBrowser::Refresh() {
} else {
topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick);
}
if (System_GetPropertyBool(SYSPROP_HAS_EXTERNAL_STORAGE)) {
if (System_GetPropertyBool(SYSPROP_HAS_ADDITIONAL_STORAGE)) {
topBar->Add(new Choice(ImageID("I_SDCARD"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::StorageClick);
}
topBar->Add(new Choice(ImageID("I_HOME"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick);
Expand Down
7 changes: 7 additions & 0 deletions UWP/PPSSPP_UWPMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ std::string System_GetProperty(SystemProperty prop) {
}
}

std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
switch (prop) {
default:
return std::vector<std::string>();
}
}

int System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
case SYSPROP_AUDIO_SAMPLE_RATE:
Expand Down
7 changes: 7 additions & 0 deletions Windows/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ std::string System_GetProperty(SystemProperty prop) {
}
}

std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
switch (prop) {
default:
return std::vector<std::string>();
}
}

// Ugly!
extern WindowsAudioBackend *winAudioBackend;

Expand Down
43 changes: 36 additions & 7 deletions android/jni/app-android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <thread>
#include <atomic>

#include <android/log.h>

#ifndef _MSC_VER
#include <jni.h>
#include <android/native_window_jni.h>
Expand Down Expand Up @@ -122,6 +124,8 @@ std::string langRegion;
std::string mogaVersion;
std::string boardName;

std::vector<std::string> g_additionalStorageDirs;

static float left_joystick_x_async;
static float left_joystick_y_async;
static float right_joystick_x_async;
Expand Down Expand Up @@ -364,6 +368,15 @@ std::string System_GetProperty(SystemProperty prop) {
}
}

std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
switch (prop) {
case SYSPROP_ADDITIONAL_STORAGE_DIRS:
return g_additionalStorageDirs;
default:
return std::vector<std::string>();
}
}

int System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
case SYSPROP_SYSTEMVERSION:
Expand Down Expand Up @@ -410,6 +423,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
return androidVersion >= 23; // 6.0 Marshmallow introduced run time permissions.
case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE:
return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature.
case SYSPROP_HAS_ADDITIONAL_STORAGE:
return !g_additionalStorageDirs.empty();
case SYSPROP_HAS_BACK_BUTTON:
return true;
case SYSPROP_HAS_IMAGE_BROWSER:
Expand Down Expand Up @@ -530,16 +545,19 @@ static void parse_args(std::vector<std::string> &args, const std::string value)
}
}

// Need to use raw Android logging before NativeInit.
#define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__)

extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
(JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam,
jstring jdataDir, jstring jexternalStorageDir, jstring jadditionalStorageDirs, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam,
jint jAndroidVersion, jstring jboard) {
setCurrentThreadName("androidInit");

// Makes sure we get early permission grants.
ProcessFrameCommands(env);

INFO_LOG(SYSTEM, "NativeApp.init() -- begin");
EARLY_LOG("NativeApp.init() -- begin");
PROFILE_INIT();

renderer_inited = false;
Expand All @@ -559,9 +577,18 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
systemName = GetJavaString(env, jmodel);
langRegion = GetJavaString(env, jlangRegion);

INFO_LOG(SYSTEM, "NativeApp.init(): device name: '%s'", systemName.c_str());
EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str());

std::string externalStorageDir = GetJavaString(env, jexternalStorageDir);
std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs);

if (!additionalStorageDirsString.empty()) {
SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs);
for (auto &str : g_additionalStorageDirs) {
EARLY_LOG("Additional storage: %s", str.c_str());
}
}

std::string externalDir = GetJavaString(env, jexternalDir);
std::string user_data_path = GetJavaString(env, jdataDir);
if (user_data_path.size() > 0)
user_data_path += "/";
Expand All @@ -570,8 +597,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
std::string cacheDir = GetJavaString(env, jcacheDir);
std::string buildBoard = GetJavaString(env, jboard);
boardName = buildBoard;
INFO_LOG(SYSTEM, "NativeApp.init(): External storage path: %s", externalDir.c_str());
INFO_LOG(SYSTEM, "NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());
EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str());
EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());

std::string app_name;
std::string app_nice_name;
Expand Down Expand Up @@ -600,7 +627,9 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
}
}

NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str());
NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str());

// No need to use EARLY_LOG anymore.

retry:
// Now that we've loaded config, set javaGL.
Expand Down
103 changes: 100 additions & 3 deletions android/src/org/ppsspp/ppsspp/NativeActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.io.File;
import java.lang.reflect.Field;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -241,6 +242,91 @@ public void setShortcutParam(String shortcutParam) {
this.shortcutParam = ((shortcutParam == null) ? "" : shortcutParam);
}

// Unofficial hacks to get a list of SD cards that are not the main "external storage".
private static List<String> getSdCardPaths(final Context context) {
// Q is the last version that will support normal file access.
List<String> list = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
Log.i(TAG, "getSdCardPaths: Trying KitKat method");
list = getSdCardPaths19(context);
}

if (list == null) {
Log.i(TAG, "getSdCardPaths: Attempting fallback");
// Try another method.
String removableStoragePath;
list = new ArrayList<String>();
File fileList[] = new File("/storage/").listFiles();
for (File file : fileList) {
if (!file.getAbsolutePath().equalsIgnoreCase(Environment.getExternalStorageDirectory().getAbsolutePath()) && file.isDirectory() && file.canRead()) {
list.add(file.getAbsolutePath());
}
}
}

// TODO: On older devices, try System.getenv(“EXTERNAL_SDCARD_STORAGE”)

if (list == null) {
return new ArrayList<String>();
} else {
return list;
}
}

/**
* returns a list of all available sd cards paths, or null if not found.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static List<String> getSdCardPaths19(final Context context)
{
final File[] externalCacheDirs = context.getExternalCacheDirs();
if (externalCacheDirs == null || externalCacheDirs.length==0)
return null;
if (externalCacheDirs.length == 1) {
if (externalCacheDirs[0] == null)
return null;
final String storageState = Environment.getStorageState(externalCacheDirs[0]);
if (!Environment.MEDIA_MOUNTED.equals(storageState))
return null;
if (Environment.isExternalStorageEmulated())
return null;
}
final List<String> result = new ArrayList<>();
if (externalCacheDirs.length == 1)
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
for (int i = 1; i < externalCacheDirs.length; ++i)
{
final File file=externalCacheDirs[i];
if (file == null)
continue;
final String storageState=Environment.getStorageState(file);
if (Environment.MEDIA_MOUNTED.equals(storageState))
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
}
if (result.isEmpty())
return null;
return result;
}

/** Given any file/folder inside an sd card, this will return the path of the sd card */
private static String getRootOfInnerSdCardFolder(File file)
{
if (file == null)
return null;
final long totalSpace = file.getTotalSpace();
while (true) {
final File parentFile = file.getParentFile();
if (parentFile == null || !parentFile.canRead()) {
break;
}
if (parentFile.getTotalSpace() != totalSpace) {
break;
}
file = parentFile;
}
return file.getAbsolutePath();
}

public void Initialize() {
// Initialize audio classes. Do this here since detectOptimalAudioSettings()
// needs audioManager
Expand Down Expand Up @@ -292,9 +378,20 @@ public void Initialize() {
isXperiaPlay = IsXperiaPlay();

String libraryDir = getApplicationLibraryDir(appInfo);
File sdcard = Environment.getExternalStorageDirectory();

String externalStorageDir = sdcard.getAbsolutePath();
String extStorageState = Environment.getExternalStorageState();
String extStorageDir = Environment.getExternalStorageDirectory().getAbsolutePath();

Log.i(TAG, "Ext storage: " + extStorageState + " " + extStorageDir);

List<String> sdCards = getSdCardPaths(this);
for (String sdcard: sdCards) {
Log.i(TAG, "SD card: " + sdcard);
}
String additionalStorageDirs = String.join(":", sdCards);

Log.i(TAG, "End of storage paths");

File filesDir = this.getFilesDir();
String dataDir = null;
if (filesDir != null) {
Expand All @@ -310,7 +407,7 @@ public void Initialize() {
overrideShortcutParam = null;

NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate);
NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcut, Build.VERSION.SDK_INT, Build.BOARD);
NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, extStorageDir, additionalStorageDirs, libraryDir, cacheDir, shortcut, Build.VERSION.SDK_INT, Build.BOARD);

// Allow C++ to tell us to use JavaGL or not.
javaGL = "true".equalsIgnoreCase(NativeApp.queryConfig("androidJavaGL"));
Expand Down
2 changes: 1 addition & 1 deletion android/src/org/ppsspp/ppsspp/NativeApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class NativeApp {
public static final int DEVICE_TYPE_TV = 1;
public static final int DEVICE_TYPE_DESKTOP = 2;

public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, String board);
public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalStorageDir, String additionalStorageDirs, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, String board);
public static native void audioInit();
public static native void audioShutdown();
public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate);
Expand Down
14 changes: 5 additions & 9 deletions headless/Headless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,11 @@ void NativeRender(GraphicsContext *graphicsContext) { }
void NativeResized() { }

std::string System_GetProperty(SystemProperty prop) { return ""; }
int System_GetPropertyInt(SystemProperty prop) {
return -1;
}
float System_GetPropertyFloat(SystemProperty prop) {
return -1;
}
bool System_GetPropertyBool(SystemProperty prop) {
return false;
}
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) { return std::vector<std::string>(); }
int System_GetPropertyInt(SystemProperty prop) { return -1; }
float System_GetPropertyFloat(SystemProperty prop) { return -1.0f; }
bool System_GetPropertyBool(SystemProperty prop) { return false; }

void System_SendMessage(const char *command, const char *parameter) {}
void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) { cb(false, ""); }
void System_AskForPermission(SystemPermission permission) {}
Expand Down
Loading

0 comments on commit 1c753e4

Please sign in to comment.