From 73855168f3f57f8a3037223235740eb7d9dd76e4 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 8 Nov 2020 23:01:59 +0100 Subject: [PATCH] Android: Show a message when adding a folder with no games To catch people who try to use unsupported formats. --- .../dolphinemu/ui/main/MainPresenter.java | 17 ++++++- .../dolphinemu/utils/ContentHandler.java | 49 ++++++++++++++----- .../dolphinemu/utils/FileBrowserHelper.java | 15 ++++-- .../app/src/main/res/values/strings.xml | 1 + .../jni/AndroidCommon/AndroidCommon.cpp | 6 +-- .../Android/jni/AndroidCommon/AndroidCommon.h | 2 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java index 92a4096bfb..ff34ea2f1b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java @@ -15,12 +15,15 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.dolphinemu.dolphinemu.BuildConfig; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.model.GameFileCache; import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; +import org.dolphinemu.dolphinemu.utils.ContentHandler; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; +import java.util.Arrays; import java.util.Set; public final class MainPresenter @@ -123,9 +126,21 @@ public final class MainPresenter public void onDirectorySelected(Intent result) { - ContentResolver contentResolver = mContext.getContentResolver(); Uri uri = result.getData(); + boolean recursive = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBooleanGlobal(); + String[] childNames = ContentHandler.getChildNames(uri, recursive); + if (Arrays.stream(childNames).noneMatch((name) -> + FileBrowserHelper.GAME_EXTENSIONS.contains(FileBrowserHelper.getExtension(name)))) + { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase); + builder.setMessage(mContext.getString(R.string.wrong_file_extension_in_directory, + FileBrowserHelper.setToSortedDelimitedString(FileBrowserHelper.GAME_EXTENSIONS))); + builder.setPositiveButton(R.string.ok, null); + builder.show(); + } + + ContentResolver contentResolver = mContext.getContentResolver(); Uri canonicalizedUri = contentResolver.canonicalize(uri); if (canonicalizedUri != null) uri = canonicalizedUri; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java index 1ae3a805d3..ebedef4b60 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java @@ -14,6 +14,7 @@ import androidx.annotation.Keep; import org.dolphinemu.dolphinemu.DolphinApplication; import java.io.FileNotFoundException; +import java.util.ArrayList; import java.util.List; /* @@ -166,26 +167,52 @@ public class ContentHandler } @NonNull @Keep - public static String[] getChildNames(@NonNull String uri) + public static String[] getChildNames(@NonNull String uri, boolean recursive) { try { - Uri unmangledUri = unmangle(uri); - String documentId = DocumentsContract.getDocumentId(treeToDocument(unmangledUri)); - Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(unmangledUri, documentId); + return getChildNames(unmangle(uri), recursive); + } + catch (Exception ignored) + { + } - final String[] projection = new String[]{Document.COLUMN_DISPLAY_NAME}; + return new String[0]; + } + + @NonNull + public static String[] getChildNames(@NonNull Uri uri, boolean recursive) + { + ArrayList result = new ArrayList<>(); + getChildNames(uri, DocumentsContract.getDocumentId(treeToDocument(uri)), recursive, result); + return result.toArray(new String[0]); + } + + private static void getChildNames(@NonNull Uri uri, @NonNull String documentId, boolean recursive, + List resultOut) + { + try + { + Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, documentId); + + final String[] projection = recursive ? new String[]{Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_MIME_TYPE, Document.COLUMN_DOCUMENT_ID} : + new String[]{Document.COLUMN_DISPLAY_NAME}; try (Cursor cursor = getContentResolver().query(childrenUri, projection, null, null, null)) { if (cursor != null) { - String[] result = new String[cursor.getCount()]; - for (int i = 0; i < result.length; i++) + while (cursor.moveToNext()) { - cursor.moveToNext(); - result[i] = cursor.getString(0); + if (recursive && Document.MIME_TYPE_DIR.equals(cursor.getString(1))) + { + getChildNames(uri, cursor.getString(2), recursive, resultOut); + } + else + { + resultOut.add(cursor.getString(0)); + } } - return result; } } } @@ -196,8 +223,6 @@ public class ContentHandler catch (Exception ignored) { } - - return new String[0]; } @NonNull diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java index dc3098df9f..f3735eabea 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java @@ -102,10 +102,8 @@ public final class FileBrowserHelper int messageId = validExtensions.size() == 1 ? R.string.wrong_file_extension_single : R.string.wrong_file_extension_multiple; - ArrayList extensionsList = new ArrayList<>(validExtensions); - Collections.sort(extensionsList); - - message = context.getString(messageId, extension, join(", ", extensionsList)); + message = context.getString(messageId, extension, + setToSortedDelimitedString(validExtensions)); } new AlertDialog.Builder(context, R.style.DolphinDialogBase) @@ -117,7 +115,7 @@ public final class FileBrowserHelper } @Nullable - private static String getExtension(@Nullable String fileName) + public static String getExtension(@Nullable String fileName) { if (fileName == null) return null; @@ -126,6 +124,13 @@ public final class FileBrowserHelper return dotIndex != -1 ? fileName.substring(dotIndex + 1) : null; } + public static String setToSortedDelimitedString(Set set) + { + ArrayList list = new ArrayList<>(set); + Collections.sort(list); + return join(", ", list); + } + // TODO: Replace this with String.join once we can use Java 8 private static String join(CharSequence delimiter, Iterable elements) { diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 4cb7c09a17..d9faf899ac 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -438,6 +438,7 @@ It can efficiently compress both junk data and encrypted Wii data. The selected file does not appear to have a file name extension.\n\nContinue anyway? The selected file has the file name extension \"%1$s\", but \"%2$s\" was expected.\n\nContinue anyway? The selected file has the file name extension \"%1$s\", but one of these extensions was expected: %2$s\n\nContinue anyway? + No compatible files were found in the selected location.\n\nThe supported formats are: %1$s Dolphin does not have permission to access one or more configured paths. Would you like to fix this before starting? diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp index b6ecd41d48..9e57d2764a 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp @@ -124,9 +124,9 @@ std::string GetAndroidContentDisplayName(const std::string& uri) std::vector GetAndroidContentChildNames(const std::string& uri) { JNIEnv* env = IDCache::GetEnvForThread(); - jobject children = - env->CallStaticObjectMethod(IDCache::GetContentHandlerClass(), - IDCache::GetContentHandlerGetChildNames(), ToJString(env, uri)); + jobject children = env->CallStaticObjectMethod(IDCache::GetContentHandlerClass(), + IDCache::GetContentHandlerGetChildNames(), + ToJString(env, uri), false); return JStringArrayToVector(env, reinterpret_cast(children)); } diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.h b/Source/Android/jni/AndroidCommon/AndroidCommon.h index a1a1643b40..1f75864704 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.h +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.h @@ -35,7 +35,7 @@ jlong GetAndroidContentSizeAndIsDirectory(const std::string& uri); // An empty string will be returned for files which do not exist. std::string GetAndroidContentDisplayName(const std::string& uri); -// Returns the display names of all children of a directory. +// Returns the display names of all children of a directory, non-recursively. std::vector GetAndroidContentChildNames(const std::string& uri); int GetNetworkIpAddress(); diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index c79007baae..a4a7ef4ebb 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -330,7 +330,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) s_content_handler_get_display_name = env->GetStaticMethodID( s_content_handler_class, "getDisplayName", "(Ljava/lang/String;)Ljava/lang/String;"); s_content_handler_get_child_names = env->GetStaticMethodID( - s_content_handler_class, "getChildNames", "(Ljava/lang/String;)[Ljava/lang/String;"); + s_content_handler_class, "getChildNames", "(Ljava/lang/String;Z)[Ljava/lang/String;"); const jclass network_helper_class = env->FindClass("org/dolphinemu/dolphinemu/utils/NetworkHelper");