From 03e24684b9ad70b2b42c98cb93fa2b5185c09495 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Thu, 23 May 2024 20:32:49 +0200 Subject: [PATCH] Android: Pass in the title through JNI, cleanup --- ...asyrpg_player_game_browser_GameScanner.cpp | 46 +++-- ..._easyrpg_player_game_browser_GameScanner.h | 2 +- .../main/java/org/easyrpg/player/Helper.java | 28 ++- .../java/org/easyrpg/player/InitActivity.java | 3 +- .../org/easyrpg/player/game_browser/Game.java | 160 +++++++----------- .../game_browser/GameBrowserActivity.java | 2 +- .../game_browser/GameBrowserHelper.java | 3 +- .../player/game_browser/GameScanner.java | 28 +-- .../settings/SettingsAudioActivity.java | 6 +- 9 files changed, 121 insertions(+), 157 deletions(-) diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp index 88263c85b2a..1cf15c8f6b0 100644 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.cpp @@ -163,15 +163,15 @@ jbyteArray readXyz(JNIEnv *env, std::istream& stream) { extern "C" JNIEXPORT jobject JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, - jstring jpath) { +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, jstring jpath, jstring jmain_dir_name) { EpAndroid::env = env; const char* path = env->GetStringUTFChars(jpath, nullptr); std::string spath(path); env->ReleaseStringUTFChars(jpath, path); - std::vector fs_list = FileFinder::FindGames(FileFinder::Root().Create(spath)); + auto root = FileFinder::Root().Create(spath); + std::vector fs_list = FileFinder::FindGames(root); jclass jgame_class = env->FindClass("org/easyrpg/player/game_browser/Game"); jobjectArray jgame_array = env->NewObjectArray(fs_list.size(), jgame_class, nullptr); @@ -180,15 +180,34 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass return jgame_array; } - jmethodID jgame_constructor = env->GetMethodID(jgame_class, "", "(Ljava/lang/String;Ljava/lang/String;[B)V"); + jmethodID jgame_constructor = env->GetMethodID(jgame_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V"); + + bool game_in_main_dir = false; + if (fs_list.size() == 1) { + if (FileFinder::GetFullFilesystemPath(root) == FileFinder::GetFullFilesystemPath(fs_list[0])) { + game_in_main_dir = true; + } + } for (size_t i = 0; i < fs_list.size(); ++i) { auto& fs = fs_list[i]; + std::string full_path = FileFinder::GetFullFilesystemPath(fs); + std::string title; + if (game_in_main_dir) { + // The main dir is URI encoded, the human readable name is in jmain_dir_name + const char* main_dir_name = env->GetStringUTFChars(jmain_dir_name, nullptr); + title = main_dir_name; + env->ReleaseStringUTFChars(jmain_dir_name, main_dir_name); + } else { + // In all other cases the folder name is "clean" and can be used + title = std::get<1>(FileFinder::GetPathAndFilename(fs.GetFullPath())); + } + std::string save_path; if (!fs.IsFeatureSupported(Filesystem::Feature::Write)) { // Is an archive and needs a redirected save path - save_path = std::get<1>(FileFinder::GetPathAndFilename(fs.GetFullPath())); + save_path = title; // compatibility with original GameScanner Java code (everything after the extension dot is removed) size_t ext = save_path.find('.'); @@ -198,17 +217,17 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass } // Very simple title graphic search: The first image in "Title" is used - auto title = fs.Subtree("Title"); + auto title_fs = fs.Subtree("Title"); jbyteArray title_image = nullptr; - if (title) { - for (auto &[name, entry]: *title.ListDirectory()) { + if (title_fs) { + for (auto &[name, entry]: *title_fs.ListDirectory()) { if (entry.type == DirectoryTree::FileType::Regular) { if (StringView(name).ends_with(".xyz")) { - auto is = title.OpenInputStream(entry.name); + auto is = title_fs.OpenInputStream(entry.name); title_image = readXyz(env, is); } else if (StringView(name).ends_with(".png") || StringView(name).ends_with(".bmp")) { - auto is = title.OpenInputStream(entry.name); + auto is = title_fs.OpenInputStream(entry.name); if (!is) { // When opening of the image fails it is an unsupported archive format // Skip this game @@ -216,7 +235,6 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass } auto vec = Utils::ReadStream(is); - title_image = env->NewByteArray(vec.size()); env->SetByteArrayRegion(title_image, 0, vec.size(), reinterpret_cast(vec.data())); @@ -226,10 +244,10 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass } // Create an instance of "Game" - jstring jgame_path = env->NewStringUTF( - ("content://" + FileFinder::GetFullFilesystemPath(fs)).c_str()); + jstring jgame_path = env->NewStringUTF(("content://" + full_path).c_str()); jstring jsave_path = env->NewStringUTF(save_path.c_str()); - jobject jgame_object = env->NewObject(jgame_class, jgame_constructor, jgame_path, jsave_path, title_image); + jstring jtitle = env->NewStringUTF(title.c_str()); + jobject jgame_object = env->NewObject(jgame_class, jgame_constructor, jgame_path, jsave_path, jtitle, title_image); env->SetObjectArrayElement(jgame_array, i, jgame_object); } diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h index 861fe4dbf38..95e56ade0a8 100644 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser_GameScanner.h @@ -10,7 +10,7 @@ extern "C" { extern "C" JNIEXPORT jobject JNICALL -Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, jstring path); +Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass clazz, jstring path, jstring jmain_dir_name); #ifdef __cplusplus } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java index 37bc969ee73..7a49df8e9d3 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java @@ -29,7 +29,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; -import java.util.Set; public class Helper { /** @@ -215,17 +214,21 @@ public static List listChildrenDocumentID(Context context, Uri folderUri return filesList; } - /** List files (with DOCUMENT_ID and MIME_TYPE) in the folder pointed by "folderURI" */ - public static List listChildrenDocumentIDAndType(Context context, Uri folderUri){ + /** + * List files in the folder pointed by "folderURI" + * @return Array of Document ID, mimeType, display name (filename) + */ + public static List listChildrenDocuments(Context context, Uri folderUri){ final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); List filesList = new ArrayList<>(); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); String mimeType = c.getString(1); - filesList.add(new String[] {documentID, mimeType}); + String fileName = c.getString(2); + filesList.add(new String[] {documentID, mimeType, fileName}); } c.close(); } catch (Exception e) { @@ -238,10 +241,10 @@ public static Uri findFileUri(Context context, Uri folderUri, String fileNameToF final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.equals(fileNameToFind)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); c.close(); @@ -261,10 +264,10 @@ public static List findFileUriWithRegex(Context context, Uri folderUri, Str final ContentResolver resolver = context.getContentResolver(); final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(folderUri, DocumentsContract.getDocumentId(folderUri)); try { - Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + Cursor c = resolver.query(childrenUri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }, null, null, null); while (c.moveToNext()) { String documentID = c.getString(0); - String fileName = getFileNameFromDocumentID(documentID); + String fileName = c.getString(1); if (fileName.matches(regex)) { Uri uri = DocumentsContract.buildDocumentUriUsingTree(folderUri, documentID); uriList.add(uri); @@ -282,13 +285,6 @@ public static DocumentFile findFile(Context context, Uri folderUri, String fileN return getFileFromURI(context, uri); } - public static String getFileNameFromDocumentID(String documentID) { - if (documentID != null) { - return documentID.substring(documentID.lastIndexOf('/') + 1); - } - return ""; - } - public static DocumentFile getFileFromURI (Context context, Uri fileURI) { try { return DocumentFile.fromTreeUri(context, fileURI); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java index 2f496668107..9a8d95202ea 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/InitActivity.java @@ -125,7 +125,8 @@ private void startGameStandalone() { String saveDir = getExternalFilesDir(null).getAbsolutePath() + "/Save"; new File(saveDir).mkdirs(); - Game project = new Game(gameDir, saveDir); + Game project = new Game(gameDir, saveDir, "", null); + project.setStandalone(true); GameBrowserHelper.launchGame(this, project); finish(); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java index b165065359f..ff836836e44 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java @@ -17,77 +17,39 @@ public class Game implements Comparable { final static char escapeCode = '\u0001'; /** The title shown in the Game Browser */ - private String title; + private String title; /** Path to the game folder (forwarded via --project-path */ private final String gameFolderPath; /** Relative path to the save directory, made absolute by launchGame (forwarded via --save-path) */ - private String savePath; + private String savePath = ""; /** Whether the game was tagged as a favourite */ - private boolean isFavorite; + private boolean isFavorite; /** Title image shown in the Game Browser */ private Bitmap titleScreen = null; /** Game is launched from the APK via standalone mode */ private boolean standalone = false; - private Game(String gameFolderPath) { - // For simplicity the gameFolderPath is an URI that is parsed - // Similar to the SafFile code - int encoded_slash_pos = gameFolderPath.lastIndexOf("%2F"); - int slash_pos = gameFolderPath.lastIndexOf("/"); - - // A file is provided when a / is after the encoded / (%2F) - if (slash_pos > -1 && slash_pos > encoded_slash_pos) { - // Extract the filename and properly encode it - this.title = gameFolderPath.substring(slash_pos + 1); - } else { - this.title = gameFolderPath.substring(encoded_slash_pos + 3); - } + public Game(String gameFolderPath, String saveFolder, String title, byte[] titleScreen) { + this.gameFolderPath = gameFolderPath; - int dot_pos = this.title.indexOf("."); - if (dot_pos > -1) { - // Strip of the file extension - this.title = this.title.substring(0, dot_pos); + // is only relative here, launchGame will put this in the "saves" directory + if (!saveFolder.isEmpty()) { + savePath = saveFolder; } - this.gameFolderPath = gameFolderPath; - this.savePath = gameFolderPath; + this.title = title; - this.isFavorite = isFavoriteFromSettings(); - } - - public Game(String gameFolderPath, byte[] titleScreen) { - this(gameFolderPath); if (titleScreen != null) { this.titleScreen = BitmapFactory.decodeByteArray(titleScreen, 0, titleScreen.length); }; - } - /** - * Constructor for standalone mode - * - * @param gameFolder - * @param saveFolder - */ - public Game(String gameFolder, String saveFolder) { - this.title = "Standalone"; - this.gameFolderPath = gameFolder; - this.savePath = saveFolder; - this.isFavorite = false; - this.standalone = true; - } - - private Game(String gameFolderPath, String saveFolder, byte[] titleScreen) { - this(gameFolderPath, titleScreen); - // is only relative here, launchGame will put this in the "saves" directory - if (!saveFolder.isEmpty()) { - savePath = saveFolder; - } + this.isFavorite = isFavoriteFromSettings(); } public static Game fromCacheEntry(Context context, String cache) { String[] entries = cache.split(String.valueOf(escapeCode)); - if (entries.length != 3) { + if (entries.length != 4) { return null; } @@ -97,65 +59,67 @@ public static Game fromCacheEntry(Context context, String cache) { return null; } + String title = entries[2]; + byte[] titleScreen = null; - if (!entries[2].equals("null")) { - titleScreen = Base64.decode(entries[2], 0); + if (!entries[3].equals("null")) { + titleScreen = Base64.decode(entries[3], 0); } - return new Game(entries[1], savePath, titleScreen); + return new Game(entries[1], savePath, title, titleScreen); } - public String getTitle() { - return title; - } + public String getTitle() { + return title; + } - public String getGameFolderPath() { - return gameFolderPath; - } + public String getGameFolderPath() { + return gameFolderPath; + } - public String getSavePath() { - return savePath; - } + public String getSavePath() { + return savePath; + } public void setSavePath(String path) { savePath = path; } - public boolean isFavorite() { - return isFavorite; - } - - public void setFavorite(boolean isFavorite) { - this.isFavorite = isFavorite; - if(isFavorite){ - SettingsManager.addFavoriteGame(this); - } else { - SettingsManager.removeAFavoriteGame(this); - } - } - - private boolean isFavoriteFromSettings() { - return SettingsManager.getFavoriteGamesList().contains(this.title); - } - - @Override - public int compareTo(Game game) { - if (this.isFavorite() && !game.isFavorite()) { - return -1; - } - if (!this.isFavorite() && game.isFavorite()) { - return 1; - } - return this.title.compareTo(game.title); - } - - public Encoding getEncoding() { + public boolean isFavorite() { + return isFavorite; + } + + public void setFavorite(boolean isFavorite) { + this.isFavorite = isFavorite; + if(isFavorite){ + SettingsManager.addFavoriteGame(this); + } else { + SettingsManager.removeAFavoriteGame(this); + } + } + + private boolean isFavoriteFromSettings() { + return SettingsManager.getFavoriteGamesList().contains(this.gameFolderPath); + } + + @Override + public int compareTo(Game game) { + if (this.isFavorite() && !game.isFavorite()) { + return -1; + } + if (!this.isFavorite() && game.isFavorite()) { + return 1; + } + return this.title.compareTo(game.title); + } + + public Encoding getEncoding() { return SettingsManager.getGameEncoding(this); - } + } - public void setEncoding(Encoding encoding) { + public void setEncoding(Encoding encoding) { SettingsManager.setGameEncoding(this, encoding); - } + } @NonNull @Override @@ -166,11 +130,13 @@ public String toString() { public String toCacheEntry() { StringBuilder sb = new StringBuilder(); + // Cache structure: savePath | gameFolderPath | title | titleScreen sb.append(savePath); sb.append(escapeCode); - sb.append(gameFolderPath); sb.append(escapeCode); + sb.append(title); + sb.append(escapeCode); if (titleScreen != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -188,7 +154,11 @@ public Bitmap getTitleScreen() { return titleScreen; } - public Boolean isStandalone() { + public boolean isStandalone() { return standalone; } + + public void setStandalone(boolean standalone) { + this.standalone = standalone; + } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java index f6232441693..5e55c475210 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java @@ -62,7 +62,7 @@ protected void onCreate(Bundle savedInstanceState) { libraryLoaded = true; } catch (UnsatisfiedLinkError e) { Log.e("EasyRPG Player", "Couldn't load libgamebrowser: " + e.getMessage()); - throw e; + throw e; } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java index 47347ce5284..e8f20f56cab 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java @@ -8,7 +8,6 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.util.Log; -import android.widget.Toast; import androidx.documentfile.provider.DocumentFile; @@ -178,7 +177,7 @@ public static SafError dealAfterFolderSelected(Activity activity, int requestCod return SafError.BAD_CONTENT_PROVIDER_BASE_FOLDER_NOT_FOUND; } - List items = Helper.listChildrenDocumentIDAndType(activity, folder.getUri()); + List items = Helper.listChildrenDocuments(activity, folder.getUri()); int item_count = 0; for (String[] item: items) { if (item[0] == null || Helper.isDirectoryFromMimeType(item[1]) || item[0].endsWith(".nomedia")) { diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java index 8c24eee870c..628089d8413 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java @@ -1,15 +1,8 @@ package org.easyrpg.player.game_browser; import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.provider.MediaStore; import android.util.Log; import android.widget.TextView; @@ -20,21 +13,10 @@ import org.easyrpg.player.settings.SettingsManager; import org.libsdl.app.SDL; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; public class GameScanner { // We use a singleton pattern to allow further optimizations @@ -145,7 +127,7 @@ private void scanGames(Activity activity){ private int scanFolderHash(Context context, Uri folderURI) { StringBuilder sb = new StringBuilder(); - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { sb.append(array[0]); sb.append(array[1]); } @@ -161,10 +143,10 @@ private void scanRootFolder(Activity activity, Uri folderURI) { final ArrayList fileURIs = new ArrayList<>(); // Precalculate how many folders are to be scanned - for (String[] array : Helper.listChildrenDocumentIDAndType(context, folderURI)) { + for (String[] array : Helper.listChildrenDocuments(context, folderURI)) { String fileDocumentID = array[0]; + String name = array[2]; - String name = Helper.getFileNameFromDocumentID(fileDocumentID); if (name.isEmpty()) { continue; } @@ -186,7 +168,7 @@ private void scanRootFolder(Activity activity, Uri folderURI) { myTextView.setText(String.format("%s (%d/%d)", name, j + 1, names.size())); }); - Game[] candidates = findGames(fileURIs.get(i).toString()); + Game[] candidates = findGames(fileURIs.get(i).toString(), names.get(i)); for (Game candidate: candidates) { if (candidate != null) { @@ -208,5 +190,5 @@ public boolean hasError() { return !errorList.isEmpty(); } - private static native Game[] findGames(String path); + private static native Game[] findGames(String path, String mainFolderName); } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java index 1fed8387687..17baef7c357 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java @@ -7,7 +7,6 @@ import android.provider.DocumentsContract; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.Button; import android.widget.LinearLayout; import android.widget.RadioButton; @@ -15,7 +14,6 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.AppCompatSpinner; import androidx.documentfile.provider.DocumentFile; import org.easyrpg.player.Helper; @@ -90,13 +88,13 @@ private List scanAvailableSoundfonts(){ Uri soundFontsFolder = SettingsManager.getSoundFontsFolderURI(this); if (soundFontsFolder != null) { - for (String[] array : Helper.listChildrenDocumentIDAndType(this, soundFontsFolder)) { + for (String[] array : Helper.listChildrenDocuments(this, soundFontsFolder)) { String fileDocumentID = array[0]; String fileDocumentType = array[1]; + String name = array[2]; // Is it a soundfont file ? boolean isDirectory = Helper.isDirectoryFromMimeType(fileDocumentType); - String name = Helper.getFileNameFromDocumentID(fileDocumentID); if (!isDirectory && name.toLowerCase().endsWith(".sf2")) { DocumentFile soundFontFile = Helper.getFileFromDocumentID(this, soundFontsFolder, fileDocumentID); if (soundFontFile != null) {