diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 238fb2a769..5655518a71 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -12,6 +12,7 @@ import android.view.Surface; import androidx.appcompat.app.AlertDialog; import org.dolphinemu.dolphinemu.activities.EmulationActivity; +import org.dolphinemu.dolphinemu.utils.CompressCallback; import org.dolphinemu.dolphinemu.utils.Log; import org.dolphinemu.dolphinemu.utils.Rumble; @@ -430,7 +431,8 @@ public final class NativeLibrary public static native boolean InstallWAD(String file); public static native boolean ConvertDiscImage(String inPath, String outPath, int platform, - int format, int blockSize, int compression, int compressionLevel, boolean scrub); + int format, int blockSize, int compression, int compressionLevel, boolean scrub, + CompressCallback callback); public static native String FormatSize(long bytes, int decimals); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java index a37a9d3874..389ef98f3e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java @@ -1,5 +1,6 @@ package org.dolphinemu.dolphinemu.fragments; +import android.app.ProgressDialog; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; @@ -108,6 +109,9 @@ public class ConvertFragment extends Fragment implements View.OnClickListener private GameFile gameFile; + private volatile boolean mCanceled; + private volatile Thread mThread = null; + public static ConvertFragment newInstance(String gamePath) { Bundle args = new Bundle(); @@ -181,6 +185,15 @@ public class ConvertFragment extends Fragment implements View.OnClickListener valueWrapper.setPosition(i); } + @Override + public void onStop() + { + super.onStop(); + + mCanceled = true; + joinThread(); + } + private Spinner populateSpinner(int spinnerId, int entriesId, int valuesId, SpinnerValue valueWrapper) { @@ -339,33 +352,88 @@ public class ConvertFragment extends Fragment implements View.OnClickListener private void convert() { + final int PROGRESS_RESOLUTION = 1000; + Context context = requireContext(); // TODO: Let the user select a path String outPath = gameFile.getPath() + ".converted"; - boolean success = NativeLibrary.ConvertDiscImage(gameFile.getPath(), outPath, - gameFile.getPlatform(), mFormat.getValue(context), mBlockSize.getValueOr(context,0), - mCompression.getValueOr(context,0), mCompressionLevel.getValueOr(context,0), - getRemoveJunkData()); + joinThread(); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); - if (success) + mCanceled = false; + + ProgressDialog progressDialog = new ProgressDialog(context, R.style.DolphinDialogBase); + + progressDialog.setTitle(R.string.convert_converting); + + progressDialog.setIndeterminate(false); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setMax(PROGRESS_RESOLUTION); + + progressDialog.setCancelable(true); + progressDialog.setOnCancelListener((dialog) -> mCanceled = true); + + progressDialog.show(); + + mThread = new Thread(() -> { - builder.setMessage(R.string.convert_success_message) - .setCancelable(false) - .setPositiveButton(R.string.ok, (dialog, i) -> + boolean success = NativeLibrary.ConvertDiscImage(gameFile.getPath(), outPath, + gameFile.getPlatform(), mFormat.getValue(context), mBlockSize.getValueOr(context, 0), + mCompression.getValueOr(context, 0), mCompressionLevel.getValueOr(context, 0), + getRemoveJunkData(), (text, completion) -> { - dialog.dismiss(); - requireActivity().finish(); + requireActivity().runOnUiThread(() -> + { + progressDialog.setMessage(text); + progressDialog.setProgress((int) (completion * PROGRESS_RESOLUTION)); + }); + + return !mCanceled; }); - } - else + + if (!mCanceled) + { + requireActivity().runOnUiThread(() -> + { + progressDialog.dismiss(); + + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); + if (success) + { + builder.setMessage(R.string.convert_success_message) + .setCancelable(false) + .setPositiveButton(R.string.ok, (dialog, i) -> + { + dialog.dismiss(); + requireActivity().finish(); + }); + } + else + { + builder.setMessage(R.string.convert_failure_message) + .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss()); + } + AlertDialog alert = builder.create(); + alert.show(); + }); + } + }); + + mThread.start(); + } + + private void joinThread() + { + if (mThread != null) { - builder.setMessage(R.string.convert_failure_message) - .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss()); + try + { + mThread.join(); + } + catch (InterruptedException ignored) + { + } } - AlertDialog alert = builder.create(); - alert.show(); } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java new file mode 100644 index 0000000000..83aa8d7919 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java @@ -0,0 +1,6 @@ +package org.dolphinemu.dolphinemu.utils; + +public interface CompressCallback +{ + boolean run(String text, float completion); +} diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 977bd56bd4..95efff4161 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -337,6 +337,7 @@ Compression Level Remove Junk Data (Irreversible) Convert + Converting Removing junk data does not save any space when converting to ISO (unless you package the ISO file in a compressed file format such as ZIP afterwards). Do you want to continue anyway? Converting Wii disc images to GCZ without removing junk data does not save any noticeable amount of space compared to converting to ISO. Do you want to continue anyway? The disc image was successfully converted. diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 8383f8ed68..0e2e9026c6 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -36,6 +36,9 @@ static jclass s_ini_file_section_class; static jfieldID s_ini_file_section_pointer; static jmethodID s_ini_file_section_constructor; +static jclass s_compress_cb_class; +static jmethodID s_compress_cb_run; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -161,6 +164,16 @@ jmethodID GetIniFileSectionConstructor() return s_ini_file_section_constructor; } +jclass GetCompressCallbackClass() +{ + return s_compress_cb_class; +} + +jmethodID GetCompressCallbackRun() +{ + return s_compress_cb_run; +} + } // namespace IDCache #ifdef __cplusplus @@ -223,6 +236,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) s_linked_hash_map_put = env->GetMethodID( s_linked_hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + const jclass compress_cb_class = + env->FindClass("org/dolphinemu/dolphinemu/utils/CompressCallback"); + s_compress_cb_class = reinterpret_cast(env->NewGlobalRef(compress_cb_class)); + s_compress_cb_run = env->GetMethodID(s_compress_cb_class, "run", "(Ljava/lang/String;F)Z"); + return JNI_VERSION; } @@ -239,6 +257,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_linked_hash_map_class); env->DeleteGlobalRef(s_ini_file_class); env->DeleteGlobalRef(s_ini_file_section_class); + env->DeleteGlobalRef(s_compress_cb_class); } #ifdef __cplusplus diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index 77f49658b8..b3dbd71554 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -38,4 +38,7 @@ jclass GetIniFileSectionClass(); jfieldID GetIniFileSectionPointer(); jmethodID GetIniFileSectionConstructor(); +jclass GetCompressCallbackClass(); +jmethodID GetCompressCallbackRun(); + } // namespace IDCache diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index bef78a49b5..4bb72ff2f6 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -27,6 +27,7 @@ #include "Common/IniFile.h" #include "Common/Logging/LogManager.h" #include "Common/MsgHandler.h" +#include "Common/ScopeGuard.h" #include "Common/Version.h" #include "Common/WindowSystemInfo.h" @@ -668,7 +669,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_InstallW JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ConvertDiscImage( JNIEnv* env, jobject obj, jstring jInPath, jstring jOutPath, jint jPlatform, jint jFormat, - jint jBlockSize, jint jCompression, jint jCompressionLevel, jboolean jScrub) + jint jBlockSize, jint jCompression, jint jCompressionLevel, jboolean jScrub, jobject jCallback) { const std::string in_path = GetJString(env, jInPath); const std::string out_path = GetJString(env, jOutPath); @@ -687,7 +688,14 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ConvertD if (!blob_reader) return static_cast(false); - const auto callback = [](const std::string& text, float percent) { return true; }; + jobject jCallbackGlobal = env->NewGlobalRef(jCallback); + Common::ScopeGuard scope_guard([jCallbackGlobal, env] { env->DeleteGlobalRef(jCallbackGlobal); }); + + const auto callback = [&jCallbackGlobal](const std::string& text, float completion) { + JNIEnv* env = IDCache::GetEnvForThread(); + return static_cast(env->CallBooleanMethod( + jCallbackGlobal, IDCache::GetCompressCallbackRun(), ToJString(env, text), completion)); + }; bool success = false;