mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-05-27 07:18:23 +00:00
Android: Add support for folder dialogs
This commit is contained in:
@@ -26,6 +26,7 @@ import android.os.Handler;
|
|||||||
import android.os.LocaleList;
|
import android.os.LocaleList;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
@@ -744,6 +745,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File dialog types
|
||||||
|
private static final int SDL_FILEDIALOG_OPENFILE = 0;
|
||||||
|
private static final int SDL_FILEDIALOG_SAVEFILE = 1;
|
||||||
|
private static final int SDL_FILEDIALOG_OPENFOLDER = 2;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
@@ -752,7 +758,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
/* This is our file dialog */
|
/* This is our file dialog */
|
||||||
String[] filelist = null;
|
String[] filelist = null;
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
Uri singleFileUri = data.getData();
|
Uri singleFileUri = data.getData();
|
||||||
|
|
||||||
if (singleFileUri == null) {
|
if (singleFileUri == null) {
|
||||||
@@ -767,6 +773,13 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
filelist[i] = uri;
|
filelist[i] = uri;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/* If the user selected a directory and the persistent permission hint has been set,
|
||||||
|
make the permission persistable */
|
||||||
|
if (mFileDialogState.type == SDL_FILEDIALOG_OPENFOLDER && mFileDialogState.persistable) {
|
||||||
|
mSingleton.getContentResolver().takePersistableUriPermission(singleFileUri,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
}
|
||||||
/* Only one file is selected. */
|
/* Only one file is selected. */
|
||||||
filelist = new String[]{singleFileUri.toString()};
|
filelist = new String[]{singleFileUri.toString()};
|
||||||
}
|
}
|
||||||
@@ -2099,19 +2112,16 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
/**
|
/**
|
||||||
* This method is called by SDL using JNI.
|
* This method is called by SDL using JNI.
|
||||||
*/
|
*/
|
||||||
public static boolean showFileDialog(String[] filters, boolean allowMultiple, boolean forWrite, int requestCode) {
|
public static boolean showFileDialog(String[] filters, boolean allowMultiple,
|
||||||
|
int type, String initialPath, int requestCode) {
|
||||||
if (mSingleton == null) {
|
if (mSingleton == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forWrite) {
|
/* Convert string list of extensions to their respective MIME types (not needed for folder selection) */
|
||||||
allowMultiple = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert string list of extensions to their respective MIME types */
|
|
||||||
ArrayList<String> mimes = new ArrayList<>();
|
ArrayList<String> mimes = new ArrayList<>();
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
if (filters != null) {
|
if (filters != null && type != SDL_FILEDIALOG_OPENFOLDER) {
|
||||||
for (String pattern : filters) {
|
for (String pattern : filters) {
|
||||||
String[] extensions = pattern.split(";");
|
String[] extensions = pattern.split(";");
|
||||||
|
|
||||||
@@ -2129,40 +2139,89 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Display the file dialog */
|
/* Handle the initial path, if set */
|
||||||
Intent intent = new Intent(forWrite ? Intent.ACTION_CREATE_DOCUMENT : Intent.ACTION_OPEN_DOCUMENT);
|
Uri initialPathUri = null;
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
|
if (initialPath != null && !initialPath.isEmpty()) {
|
||||||
switch (mimes.size()) {
|
try {
|
||||||
case 0:
|
initialPathUri = Uri.parse(initialPath);
|
||||||
intent.setType("*/*");
|
} catch (Exception e) {
|
||||||
break;
|
Log.e(TAG, "Failed to parse initial path URI, ignoring initial path", e);
|
||||||
case 1:
|
}
|
||||||
intent.setType(mimes.get(0));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
intent.setType("*/*");
|
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.toArray(new String[]{}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean persistable = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_ALLOW_PERSISTENT_FOLDER_ACCESS", false);
|
||||||
|
|
||||||
|
/* Select the intent based on the type */
|
||||||
|
String action;
|
||||||
|
switch (type) {
|
||||||
|
case SDL_FILEDIALOG_OPENFILE:
|
||||||
|
action = Intent.ACTION_OPEN_DOCUMENT;
|
||||||
|
break;
|
||||||
|
case SDL_FILEDIALOG_SAVEFILE:
|
||||||
|
action = Intent.ACTION_CREATE_DOCUMENT;
|
||||||
|
allowMultiple = false;
|
||||||
|
break;
|
||||||
|
case SDL_FILEDIALOG_OPENFOLDER:
|
||||||
|
action = Intent.ACTION_OPEN_DOCUMENT_TREE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Unsupported file dialog type: " + type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare the intent with the proper values */
|
||||||
|
Intent intent = new Intent(action);
|
||||||
|
if (type != SDL_FILEDIALOG_OPENFOLDER) {
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
|
||||||
|
switch (mimes.size()) {
|
||||||
|
case 0:
|
||||||
|
intent.setType("*/*");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
intent.setType(mimes.get(0));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
intent.setType("*/*");
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.toArray(new String[]{}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int intent_flags = Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||||
|
if (persistable) {
|
||||||
|
intent_flags |= Intent.FLAG_GRANT_PREFIX_URI_PERMISSION |
|
||||||
|
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
|
||||||
|
}
|
||||||
|
intent.addFlags(intent_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPathUri != null) {
|
||||||
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialPathUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display the file/folder dialog */
|
||||||
try {
|
try {
|
||||||
mSingleton.startActivityForResult(intent, requestCode);
|
mSingleton.startActivityForResult(intent, requestCode);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
Log.e(TAG, "Unable to open file dialog.", e);
|
Log.e(TAG, "Unable to open dialog.", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save current dialog state */
|
/* Save current dialog state */
|
||||||
mFileDialogState = new SDLFileDialogState();
|
mFileDialogState = new SDLFileDialogState();
|
||||||
mFileDialogState.requestCode = requestCode;
|
mFileDialogState.requestCode = requestCode;
|
||||||
mFileDialogState.multipleChoice = allowMultiple;
|
mFileDialogState.type = type;
|
||||||
|
mFileDialogState.persistable = persistable;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal class used to track active open file dialog */
|
/* Internal class used to track active file dialog */
|
||||||
static class SDLFileDialogState {
|
static class SDLFileDialogState {
|
||||||
int requestCode;
|
int requestCode;
|
||||||
boolean multipleChoice;
|
int type;
|
||||||
|
boolean persistable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -140,6 +140,24 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
|
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variable to control whether we allow persistent folder access on Android when using the SDL select folder dialog.
|
||||||
|
*
|
||||||
|
* If set to `1`, the selected folder will be accessible persistently across app launches.
|
||||||
|
* That allows the user to only have to select the directory once, and then the app can access it again in the future
|
||||||
|
* without needing to ask the user to select it again.
|
||||||
|
*
|
||||||
|
* The variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* - "0": Persistent folder access is not allowed. (default)
|
||||||
|
* - "1": Persistent folder access is allowed.
|
||||||
|
*
|
||||||
|
* This hint should be set before the SDL folder selection dialog is shown, and can be changed between dialog invocations.
|
||||||
|
*
|
||||||
|
* \since This hint is available since SDL 3.6.0.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_ANDROID_ALLOW_PERSISTENT_FOLDER_ACCESS "SDL_ANDROID_ALLOW_PERSISTENT_FOLDER_ACCESS"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A variable setting the app ID string.
|
* A variable setting the app ID string.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
|
|||||||
midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z");
|
midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z");
|
||||||
midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
|
midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
|
||||||
midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
|
midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
|
||||||
midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z");
|
midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZILjava/lang/String;I)Z");
|
||||||
midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;");
|
midGetPreferredLocales = (*env)->GetStaticMethodID(env, mActivityClass, "getPreferredLocales", "()Ljava/lang/String;");
|
||||||
|
|
||||||
if (!midClipboardGetText ||
|
if (!midClipboardGetText ||
|
||||||
@@ -3386,18 +3386,34 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Android_JNI_OpenFileDialog(
|
bool Android_JNI_ShowFileDialog(
|
||||||
SDL_DialogFileCallback callback, void *userdata,
|
SDL_DialogFileCallback callback, void *userdata,
|
||||||
const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
|
const SDL_DialogFileFilter *filters, int nfilters, SDL_FileDialogType type,
|
||||||
bool multiple)
|
bool multiple, const char *initialPath)
|
||||||
{
|
{
|
||||||
if (mAndroidFileDialogData.callback != NULL) {
|
if (mAndroidFileDialogData.callback != NULL) {
|
||||||
SDL_SetError("Only one file dialog can be run at a time.");
|
SDL_SetError("Only one file dialog can be run at a time.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forwrite) {
|
// Setup type
|
||||||
|
int dialogType = 0;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case SDL_FILEDIALOG_OPENFILE:
|
||||||
|
dialogType = 0;
|
||||||
|
break;
|
||||||
|
case SDL_FILEDIALOG_SAVEFILE:
|
||||||
multiple = false;
|
multiple = false;
|
||||||
|
dialogType = 1;
|
||||||
|
break;
|
||||||
|
case SDL_FILEDIALOG_OPENFOLDER:
|
||||||
|
multiple = false;
|
||||||
|
dialogType = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_SetError("Invalid file dialog type");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEnv *env = Android_JNI_GetEnv();
|
JNIEnv *env = Android_JNI_GetEnv();
|
||||||
@@ -3417,6 +3433,12 @@ bool Android_JNI_OpenFileDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup initial path
|
||||||
|
jstring initialPathString = NULL;
|
||||||
|
if (initialPath && *initialPath) {
|
||||||
|
initialPathString = (*env)->NewStringUTF(env, initialPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup data
|
// Setup data
|
||||||
static SDL_AtomicInt next_request_code;
|
static SDL_AtomicInt next_request_code;
|
||||||
mAndroidFileDialogData.request_code = SDL_AddAtomicInt(&next_request_code, 1);
|
mAndroidFileDialogData.request_code = SDL_AddAtomicInt(&next_request_code, 1);
|
||||||
@@ -3425,8 +3447,10 @@ bool Android_JNI_OpenFileDialog(
|
|||||||
|
|
||||||
// Invoke JNI
|
// Invoke JNI
|
||||||
jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass,
|
jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass,
|
||||||
midShowFileDialog, filtersArray, (jboolean) multiple, (jboolean) forwrite, mAndroidFileDialogData.request_code);
|
midShowFileDialog, filtersArray, (jboolean) multiple,
|
||||||
|
dialogType, initialPathString, mAndroidFileDialogData.request_code);
|
||||||
(*env)->DeleteLocalRef(env, filtersArray);
|
(*env)->DeleteLocalRef(env, filtersArray);
|
||||||
|
(*env)->DeleteLocalRef(env, initialPathString);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
mAndroidFileDialogData.callback = NULL;
|
mAndroidFileDialogData.callback = NULL;
|
||||||
SDL_AddAtomicInt(&next_request_code, -1);
|
SDL_AddAtomicInt(&next_request_code, -1);
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ bool SDL_IsAndroidTablet(void);
|
|||||||
bool SDL_IsAndroidTV(void);
|
bool SDL_IsAndroidTV(void);
|
||||||
|
|
||||||
// File Dialogs
|
// File Dialogs
|
||||||
bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void *userdata,
|
bool Android_JNI_ShowFileDialog(SDL_DialogFileCallback callback, void *userdata,
|
||||||
const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
|
const SDL_DialogFileFilter *filters, int nfilters, SDL_FileDialogType type,
|
||||||
bool multiple);
|
bool multiple, const char *initialPath);
|
||||||
|
|
||||||
// Ends C function definitions when using C++
|
// Ends C function definitions when using C++
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil
|
|||||||
SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
|
SDL_DialogFileFilter *filters = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, NULL);
|
||||||
int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
|
int nfilters = (int) SDL_GetNumberProperty(props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, 0);
|
||||||
bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
|
bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
|
||||||
bool is_save;
|
const char *base_folder = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
|
||||||
|
|
||||||
if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
|
if (SDL_GetHint(SDL_HINT_FILE_DIALOG_DRIVER) != NULL) {
|
||||||
SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
|
SDL_SetError("File dialog driver unsupported (don't set SDL_HINT_FILE_DIALOG_DRIVER)");
|
||||||
@@ -36,22 +36,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
if (!Android_JNI_ShowFileDialog(callback, userdata, filters, nfilters, type, allow_many, base_folder)) {
|
||||||
case SDL_FILEDIALOG_OPENFILE:
|
|
||||||
is_save = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_FILEDIALOG_SAVEFILE:
|
|
||||||
is_save = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_FILEDIALOG_OPENFOLDER:
|
|
||||||
SDL_Unsupported();
|
|
||||||
callback(userdata, NULL, -1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Android_JNI_OpenFileDialog(callback, userdata, filters, nfilters, is_save, allow_many)) {
|
|
||||||
// SDL_SetError is already called when it fails
|
// SDL_SetError is already called when it fails
|
||||||
callback(userdata, NULL, -1);
|
callback(userdata, NULL, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user