From 7d6debb90793dbd34df49d779ea2c34e432094d8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 25 Jun 2020 00:02:02 +0200 Subject: Android: Add disc image conversion --- .../org/dolphinemu/dolphinemu/NativeLibrary.java | 3 + .../dolphinemu/activities/ConvertActivity.java | 40 +++ .../dolphinemu/dialogs/GamePropertiesDialog.java | 14 +- .../dolphinemu/fragments/ConvertFragment.java | 371 +++++++++++++++++++++ .../org/dolphinemu/dolphinemu/model/GameFile.java | 4 + 5 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java (limited to 'Source/Android/app/src/main/java/org') 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 86e75d55ec..238fb2a769 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 @@ -429,6 +429,9 @@ 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); + public static native String FormatSize(long bytes, int decimals); private static boolean alertResult = false; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java new file mode 100644 index 0000000000..40cf22397b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java @@ -0,0 +1,40 @@ +package org.dolphinemu.dolphinemu.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.fragments.ConvertFragment; + +import androidx.appcompat.app.AppCompatActivity; + +public class ConvertActivity extends AppCompatActivity +{ + private static final String ARG_GAME_PATH = "game_path"; + + public static void launch(Context context, String gamePath) + { + Intent launcher = new Intent(context, ConvertActivity.class); + launcher.putExtra(ARG_GAME_PATH, gamePath); + context.startActivity(launcher); + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_convert); + + String path = getIntent().getStringExtra(ARG_GAME_PATH); + + ConvertFragment fragment = (ConvertFragment) getSupportFragmentManager() + .findFragmentById(R.id.fragment_convert); + if (fragment == null) + { + fragment = ConvertFragment.newInstance(path); + getSupportFragmentManager().beginTransaction().add(R.id.fragment_convert, fragment).commit(); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java index 15521e6043..44642d30b8 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.activities.ConvertActivity; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; @@ -67,6 +68,9 @@ public class GamePropertiesDialog extends DialogFragment .getSupportFragmentManager(), "game_details"); break; case 1: + ConvertActivity.launch(getContext(), path); + break; + case 2: try (Settings settings = new Settings()) { settings.loadSettings(null); @@ -74,23 +78,23 @@ public class GamePropertiesDialog extends DialogFragment settings.saveSettings(null, getContext()); } break; - case 2: + case 3: SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision); break; - case 3: + case 4: SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision); break; - case 4: + case 5: SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision); break; - case 5: + case 6: // Clear option for GC, Wii controls for else if (platform == Platform.GAMECUBE.toInt()) clearGameSettings(gameId); else SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId, revision); break; - case 6: + case 7: clearGameSettings(gameId); break; } 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 new file mode 100644 index 0000000000..a37a9d3874 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java @@ -0,0 +1,371 @@ +package org.dolphinemu.dolphinemu.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.Spinner; + +import org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.model.GameFile; +import org.dolphinemu.dolphinemu.services.GameFileCacheService; +import org.dolphinemu.dolphinemu.ui.platform.Platform; + +import java.util.ArrayList; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +public class ConvertFragment extends Fragment implements View.OnClickListener +{ + private static class SpinnerValue implements AdapterView.OnItemSelectedListener + { + private int mValuesId = -1; + private int mCurrentPosition = -1; + private ArrayList mCallbacks = new ArrayList<>(); + + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long id) + { + if (mCurrentPosition != position) + setPosition(position); + } + + @Override + public void onNothingSelected(AdapterView adapterView) + { + mCurrentPosition = -1; + } + + int getPosition() + { + return mCurrentPosition; + } + + void setPosition(int position) + { + mCurrentPosition = position; + for (Runnable callback : mCallbacks) + callback.run(); + } + + void populate(int valuesId) + { + mValuesId = valuesId; + } + + boolean hasValues() + { + return mValuesId != -1; + } + + int getValue(Context context) + { + return context.getResources().getIntArray(mValuesId)[mCurrentPosition]; + } + + int getValueOr(Context context, int defaultValue) + { + return hasValues() ? getValue(context) : defaultValue; + } + + void addCallback(Runnable callback) + { + mCallbacks.add(callback); + } + } + + private static final String ARG_GAME_PATH = "game_path"; + + private static final String KEY_FORMAT = "convert_format"; + private static final String KEY_BLOCK_SIZE = "convert_block_size"; + private static final String KEY_COMPRESSION = "convert_compression"; + private static final String KEY_COMPRESSION_LEVEL = "convert_compression_level"; + private static final String KEY_REMOVE_JUNK_DATA = "remove_junk_data"; + + private static final int BLOB_TYPE_PLAIN = 0; + private static final int BLOB_TYPE_GCZ = 3; + private static final int BLOB_TYPE_WIA = 7; + private static final int BLOB_TYPE_RVZ = 8; + + private static final int COMPRESSION_NONE = 0; + private static final int COMPRESSION_PURGE = 1; + private static final int COMPRESSION_BZIP2 = 2; + private static final int COMPRESSION_LZMA = 3; + private static final int COMPRESSION_LZMA2 = 4; + private static final int COMPRESSION_ZSTD = 5; + + private SpinnerValue mFormat = new SpinnerValue(); + private SpinnerValue mBlockSize = new SpinnerValue(); + private SpinnerValue mCompression = new SpinnerValue(); + private SpinnerValue mCompressionLevel = new SpinnerValue(); + + private GameFile gameFile; + + public static ConvertFragment newInstance(String gamePath) + { + Bundle args = new Bundle(); + args.putString(ARG_GAME_PATH, gamePath); + + ConvertFragment fragment = new ConvertFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + gameFile = GameFileCacheService.addOrGet(requireArguments().getString(ARG_GAME_PATH)); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_convert, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) + { + populateFormats(); + populateBlockSize(); + populateCompression(); + populateCompressionLevel(); + populateRemoveJunkData(); + + mFormat.addCallback(this::populateBlockSize); + mFormat.addCallback(this::populateCompression); + mCompression.addCallback(this::populateCompressionLevel); + mFormat.addCallback(this::populateRemoveJunkData); + + view.findViewById(R.id.button_convert).setOnClickListener(this); + + if (savedInstanceState != null) + { + setSpinnerSelection(R.id.spinner_format, mFormat, savedInstanceState.getInt(KEY_FORMAT)); + setSpinnerSelection(R.id.spinner_block_size, mBlockSize, + savedInstanceState.getInt(KEY_BLOCK_SIZE)); + setSpinnerSelection(R.id.spinner_compression, mCompression, + savedInstanceState.getInt(KEY_COMPRESSION)); + setSpinnerSelection(R.id.spinner_compression_level, mCompressionLevel, + savedInstanceState.getInt(KEY_COMPRESSION_LEVEL)); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + removeJunkData.setChecked(savedInstanceState.getBoolean(KEY_REMOVE_JUNK_DATA)); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) + { + outState.putInt(KEY_FORMAT, mFormat.getPosition()); + outState.putInt(KEY_BLOCK_SIZE, mBlockSize.getPosition()); + outState.putInt(KEY_COMPRESSION, mCompression.getPosition()); + outState.putInt(KEY_COMPRESSION_LEVEL, mCompressionLevel.getPosition()); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + outState.putBoolean(KEY_REMOVE_JUNK_DATA, removeJunkData.isChecked()); + } + + private void setSpinnerSelection(int id, SpinnerValue valueWrapper, int i) + { + ((Spinner) requireView().findViewById(id)).setSelection(i); + valueWrapper.setPosition(i); + } + + private Spinner populateSpinner(int spinnerId, int entriesId, int valuesId, + SpinnerValue valueWrapper) + { + Spinner spinner = requireView().findViewById(spinnerId); + + ArrayAdapter adapter = ArrayAdapter.createFromResource(requireContext(), + entriesId, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + + spinner.setEnabled(spinner.getCount() > 1); + + valueWrapper.populate(valuesId); + valueWrapper.setPosition(spinner.getSelectedItemPosition()); + spinner.setOnItemSelectedListener(valueWrapper); + + return spinner; + } + + private Spinner clearSpinner(int spinnerId, SpinnerValue valueWrapper) + { + Spinner spinner = requireView().findViewById(spinnerId); + + spinner.setAdapter(null); + + spinner.setEnabled(false); + + valueWrapper.populate(-1); + valueWrapper.setPosition(-1); + spinner.setOnItemSelectedListener(valueWrapper); + + return spinner; + } + + private void populateFormats() + { + Spinner spinner = populateSpinner(R.id.spinner_format, R.array.convertFormatEntries, + R.array.convertFormatValues, mFormat); + + if (gameFile.getBlobType() == BLOB_TYPE_PLAIN) + spinner.setSelection(spinner.getCount() - 1); + } + + private void populateBlockSize() + { + switch (mFormat.getValue(requireContext())) + { + case BLOB_TYPE_GCZ: + // In the equivalent DolphinQt code, we have some logic for avoiding block sizes that can + // trigger bugs in Dolphin versions older than 5.0-11893, but it was too annoying to port. + // TODO: Port it? + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeGczEntries, + R.array.convertBlockSizeGczValues, mBlockSize); + break; + case BLOB_TYPE_WIA: + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeWiaEntries, + R.array.convertBlockSizeWiaValues, mBlockSize); + break; + case BLOB_TYPE_RVZ: + populateSpinner(R.id.spinner_block_size, R.array.convertBlockSizeRvzEntries, + R.array.convertBlockSizeRvzValues, mBlockSize).setSelection(2); + break; + default: + clearSpinner(R.id.spinner_block_size, mBlockSize); + } + } + + private void populateCompression() + { + switch (mFormat.getValue(requireContext())) + { + case BLOB_TYPE_GCZ: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionGczEntries, + R.array.convertCompressionGczValues, mCompression); + break; + case BLOB_TYPE_WIA: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionWiaEntries, + R.array.convertCompressionWiaValues, mCompression); + break; + case BLOB_TYPE_RVZ: + populateSpinner(R.id.spinner_compression, R.array.convertCompressionRvzEntries, + R.array.convertCompressionRvzValues, mCompression).setSelection(4); + break; + default: + clearSpinner(R.id.spinner_compression, mCompression); + } + } + + private void populateCompressionLevel() + { + switch (mCompression.getValueOr(requireContext(), COMPRESSION_NONE)) + { + case COMPRESSION_BZIP2: + case COMPRESSION_LZMA: + case COMPRESSION_LZMA2: + populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelEntries, + R.array.convertCompressionLevelValues, mCompressionLevel).setSelection(4); + break; + case COMPRESSION_ZSTD: + // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt? + populateSpinner(R.id.spinner_compression_level, R.array.convertCompressionLevelZstdEntries, + R.array.convertCompressionLevelZstdValues, mCompressionLevel).setSelection(4); + break; + default: + clearSpinner(R.id.spinner_compression_level, mCompressionLevel); + } + } + + private void populateRemoveJunkData() + { + boolean scrubbingAllowed = mFormat.getValue(requireContext()) != BLOB_TYPE_RVZ && + !gameFile.isDatelDisc(); + + CheckBox removeJunkData = requireView().findViewById(R.id.checkbox_remove_junk_data); + removeJunkData.setEnabled(scrubbingAllowed); + if (!scrubbingAllowed) + removeJunkData.setChecked(false); + } + + private boolean getRemoveJunkData() + { + CheckBox checkBoxScrub = requireView().findViewById(R.id.checkbox_remove_junk_data); + return checkBoxScrub.isChecked(); + } + + @Override + public void onClick(View view) + { + Context context = requireContext(); + + boolean scrub = getRemoveJunkData(); + int format = mFormat.getValue(context); + + boolean iso_warning = scrub && format == BLOB_TYPE_PLAIN; + boolean gcz_warning = !scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() && + gameFile.getPlatform() == Platform.WII.toInt(); + + if (iso_warning || gcz_warning) + { + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); + builder.setMessage(iso_warning ? R.string.convert_warning_iso : R.string.convert_warning_gcz) + .setPositiveButton(R.string.yes, (dialog, i) -> + { + dialog.dismiss(); + convert(); + }) + .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss()); + AlertDialog alert = builder.create(); + alert.show(); + } + else + { + convert(); + } + } + + private void convert() + { + 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()); + + 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(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java index dd1dd2898e..c0b55f5da7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java @@ -38,6 +38,8 @@ public class GameFile public native int getRevision(); + public native int getBlobType(); + public native String getBlobTypeString(); public native long getBlockSize(); @@ -48,6 +50,8 @@ public class GameFile public native long getFileSize(); + public native boolean isDatelDisc(); + public native int[] getBanner(); public native int getBannerWidth(); -- cgit v1.2.3 From d9f3e382fe1b2f1c7d9de54cefa920b4f0502af6 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 25 Jun 2020 19:38:02 +0200 Subject: Android: Add a progress dialog for disc image conversion --- .../org/dolphinemu/dolphinemu/NativeLibrary.java | 4 +- .../dolphinemu/fragments/ConvertFragment.java | 102 +++++++++++++++++---- .../dolphinemu/utils/CompressCallback.java | 6 ++ 3 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CompressCallback.java (limited to 'Source/Android/app/src/main/java/org') 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(); + + 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); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.DolphinDialogBase); - if (success) + 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); +} -- cgit v1.2.3 From 6e1e6d2311df787e800be199df369b5d12aa6cae Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 26 Jun 2020 17:52:31 +0200 Subject: Android: Add content provider support to File::IOFile Taking the hit now to prepare us for when Google Play will force us to use scoped storage... --- .../dolphinemu/utils/ContentHandler.java | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java (limited to 'Source/Android/app/src/main/java/org') 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 new file mode 100644 index 0000000000..a42c464d52 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java @@ -0,0 +1,23 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.net.Uri; + +import org.dolphinemu.dolphinemu.DolphinApplication; + +import java.io.FileNotFoundException; + +public class ContentHandler +{ + public static int openFd(String uri, String mode) + { + try + { + return DolphinApplication.getAppContext().getContentResolver() + .openFileDescriptor(Uri.parse(uri), mode).detachFd(); + } + catch (FileNotFoundException | NullPointerException e) + { + return -1; + } + } +} -- cgit v1.2.3 From 3805b84906b7994486c1b13eb49f93b2796a9a11 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 27 Jun 2020 11:04:48 +0200 Subject: Android: Add content provider support to File::Delete --- .../org/dolphinemu/dolphinemu/utils/ContentHandler.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'Source/Android/app/src/main/java/org') 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 a42c464d52..83b9c585c9 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 @@ -1,6 +1,8 @@ package org.dolphinemu.dolphinemu.utils; +import android.content.ContentResolver; import android.net.Uri; +import android.provider.DocumentsContract; import org.dolphinemu.dolphinemu.DolphinApplication; @@ -20,4 +22,18 @@ public class ContentHandler return -1; } } + + public static boolean delete(String uri) + { + try + { + ContentResolver resolver = DolphinApplication.getAppContext().getContentResolver(); + return DocumentsContract.deleteDocument(resolver, Uri.parse(uri)); + } + catch (FileNotFoundException e) + { + // Return true because we care about the file not being there, not the actual delete. + return true; + } + } } -- cgit v1.2.3 From 8c999cf3b131cf65f758fc09dde05451e8bfa075 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 26 Jun 2020 17:52:35 +0200 Subject: Android: Let the user select where to save disc images --- .../dolphinemu/fragments/ConvertFragment.java | 58 +++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) (limited to 'Source/Android/app/src/main/java/org') 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 389ef98f3e..700721fd03 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,8 +1,12 @@ package org.dolphinemu.dolphinemu.fragments; +import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; +import android.os.Build; import android.os.Bundle; +import android.provider.DocumentsContract; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,6 +21,7 @@ import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.ui.platform.Platform; +import java.io.File; import java.util.ArrayList; import androidx.annotation.NonNull; @@ -90,6 +95,8 @@ public class ConvertFragment extends Fragment implements View.OnClickListener private static final String KEY_COMPRESSION_LEVEL = "convert_compression_level"; private static final String KEY_REMOVE_JUNK_DATA = "remove_junk_data"; + private static final int REQUEST_CODE_SAVE_FILE = 0; + private static final int BLOB_TYPE_PLAIN = 0; private static final int BLOB_TYPE_GCZ = 3; private static final int BLOB_TYPE_WIA = 7; @@ -338,7 +345,7 @@ public class ConvertFragment extends Fragment implements View.OnClickListener .setPositiveButton(R.string.yes, (dialog, i) -> { dialog.dismiss(); - convert(); + showSavePrompt(); }) .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss()); AlertDialog alert = builder.create(); @@ -346,19 +353,58 @@ public class ConvertFragment extends Fragment implements View.OnClickListener } else { - convert(); + showSavePrompt(); } } - private void convert() + private void showSavePrompt() + { + String originalPath = gameFile.getPath(); + + StringBuilder path = new StringBuilder(new File(originalPath).getName()); + int dotIndex = path.lastIndexOf("."); + if (dotIndex != -1) + path.setLength(dotIndex); + switch (mFormat.getValue(requireContext())) + { + case BLOB_TYPE_PLAIN: + path.append(".iso"); + break; + case BLOB_TYPE_GCZ: + path.append(".gcz"); + break; + case BLOB_TYPE_WIA: + path.append(".wia"); + break; + case BLOB_TYPE_RVZ: + path.append(".rvz"); + break; + } + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + intent.putExtra(Intent.EXTRA_TITLE, path.toString()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, originalPath); + startActivityForResult(intent, REQUEST_CODE_SAVE_FILE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == Activity.RESULT_OK) + { + convert(data.getData().toString()); + } + } + + private void convert(String outPath) { final int PROGRESS_RESOLUTION = 1000; Context context = requireContext(); - // TODO: Let the user select a path - String outPath = gameFile.getPath() + ".converted"; - joinThread(); mCanceled = false; -- cgit v1.2.3 From 5d13f3675f9907459f107e951d39713628e51177 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 23 Jun 2020 18:31:39 +0200 Subject: Android: Pass GameFile to GamePropertiesDialog::newInstance --- .../dolphinemu/dolphinemu/adapters/GameAdapter.java | 4 +--- .../dolphinemu/adapters/GameRowPresenter.java | 4 +--- .../dolphinemu/dialogs/GamePropertiesDialog.java | 18 +++++++++--------- 3 files changed, 11 insertions(+), 15 deletions(-) (limited to 'Source/Android/app/src/main/java/org') diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java index ff2db6b6a5..5fd27ad5ab 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java @@ -156,9 +156,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl return true; } - GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(), - gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform()); - + GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile); ((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction() .add(fragment, GamePropertiesDialog.TAG).commit(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java index 6790f331b2..aa600173f4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java @@ -100,9 +100,7 @@ public final class GameRowPresenter extends Presenter return true; } - GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile.getPath(), - gameId, holder.gameFile.getRevision(), holder.gameFile.getPlatform()); - + GamePropertiesDialog fragment = GamePropertiesDialog.newInstance(holder.gameFile); ((FragmentActivity) view.getContext()).getSupportFragmentManager().beginTransaction() .add(fragment, GamePropertiesDialog.TAG).commit(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java index 44642d30b8..9e4f7a535b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java @@ -14,6 +14,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; +import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.Log; @@ -23,21 +24,20 @@ import java.io.File; public class GamePropertiesDialog extends DialogFragment { public static final String TAG = "GamePropertiesDialog"; - public static final String ARG_PATH = "path"; - public static final String ARG_GAMEID = "game_id"; + private static final String ARG_PATH = "path"; + private static final String ARG_GAMEID = "game_id"; public static final String ARG_REVISION = "revision"; - public static final String ARG_PLATFORM = "platform"; + private static final String ARG_PLATFORM = "platform"; - public static GamePropertiesDialog newInstance(String path, String gameId, int revision, - int platform) + public static GamePropertiesDialog newInstance(GameFile gameFile) { GamePropertiesDialog fragment = new GamePropertiesDialog(); Bundle arguments = new Bundle(); - arguments.putString(ARG_PATH, path); - arguments.putString(ARG_GAMEID, gameId); - arguments.putInt(ARG_REVISION, revision); - arguments.putInt(ARG_PLATFORM, platform); + arguments.putString(ARG_PATH, gameFile.getPath()); + arguments.putString(ARG_GAMEID, gameFile.getGameId()); + arguments.putInt(ARG_REVISION, gameFile.getRevision()); + arguments.putInt(ARG_PLATFORM, gameFile.getPlatform()); fragment.setArguments(arguments); return fragment; -- cgit v1.2.3 From 23ea47d4dfb8ea4ac2b33aa8c5650110dbb0aa89 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 27 Jun 2020 12:20:58 +0200 Subject: Android: Refactor GamePropertiesDialog.onCreateDialog --- .../dolphinemu/dialogs/GamePropertiesDialog.java | 89 ++++++++++------------ .../dolphinemu/utils/AlertDialogItemsBuilder.java | 40 ++++++++++ 2 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java (limited to 'Source/Android/app/src/main/java/org') diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java index 9e4f7a535b..d3f5395107 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java @@ -16,6 +16,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.ui.platform.Platform; +import org.dolphinemu.dolphinemu.utils.AlertDialogItemsBuilder; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.Log; @@ -47,62 +48,56 @@ public class GamePropertiesDialog extends DialogFragment @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(), - R.style.DolphinDialogBase); - String path = requireArguments().getString(ARG_PATH); String gameId = requireArguments().getString(ARG_GAMEID); int revision = requireArguments().getInt(ARG_REVISION); int platform = requireArguments().getInt(ARG_PLATFORM); + AlertDialogItemsBuilder itemsBuilder = new AlertDialogItemsBuilder(requireContext()); + + itemsBuilder.add(R.string.properties_details, (dialog, i) -> + GameDetailsDialog.newInstance(path).show(requireActivity() + .getSupportFragmentManager(), "game_details")); + + itemsBuilder.add(R.string.properties_convert, (dialog, i) -> + ConvertActivity.launch(getContext(), path)); + + itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) -> + { + try (Settings settings = new Settings()) + { + settings.loadSettings(null); + StringSetting.MAIN_DEFAULT_ISO.setString(settings, path); + settings.saveSettings(null, getContext()); + } + }); + + itemsBuilder.add(R.string.properties_core_settings, (dialog, i) -> + SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision)); + + itemsBuilder.add(R.string.properties_gfx_settings, (dialog, i) -> + SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision)); + + itemsBuilder.add(R.string.properties_gc_controller, (dialog, i) -> + SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision)); + + if (platform != Platform.GAMECUBE.toInt()) + { + itemsBuilder.add(R.string.properties_wii_controller, (dialog, i) -> + SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId, revision)); + } + + itemsBuilder.add(R.string.properties_clear_game_settings, (dialog, i) -> + clearGameSettings(gameId)); + + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(), + R.style.DolphinDialogBase); + itemsBuilder.applyToBuilder(builder); builder.setTitle(requireContext() - .getString(R.string.preferences_game_properties) + ": " + gameId) - .setItems(platform == Platform.GAMECUBE.toInt() ? - R.array.gameSettingsMenusGC : - R.array.gameSettingsMenusWii, (dialog, which) -> - { - switch (which) - { - case 0: - GameDetailsDialog.newInstance(path).show((requireActivity()) - .getSupportFragmentManager(), "game_details"); - break; - case 1: - ConvertActivity.launch(getContext(), path); - break; - case 2: - try (Settings settings = new Settings()) - { - settings.loadSettings(null); - StringSetting.MAIN_DEFAULT_ISO.setString(settings, path); - settings.saveSettings(null, getContext()); - } - break; - case 3: - SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId, revision); - break; - case 4: - SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId, revision); - break; - case 5: - SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId, revision); - break; - case 6: - // Clear option for GC, Wii controls for else - if (platform == Platform.GAMECUBE.toInt()) - clearGameSettings(gameId); - else - SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId, revision); - break; - case 7: - clearGameSettings(gameId); - break; - } - }); + .getString(R.string.preferences_game_properties) + ": " + gameId); return builder.create(); } - private void clearGameSettings(String gameId) { String gameSettingsPath = diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java new file mode 100644 index 0000000000..0c4f358069 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AlertDialogItemsBuilder.java @@ -0,0 +1,40 @@ +package org.dolphinemu.dolphinemu.utils; + +import androidx.appcompat.app.AlertDialog; + +import android.content.Context; +import android.content.DialogInterface.OnClickListener; + +import java.util.ArrayList; + +public class AlertDialogItemsBuilder +{ + private Context mContext; + + private ArrayList mLabels = new ArrayList<>(); + private ArrayList mListeners = new ArrayList<>(); + + public AlertDialogItemsBuilder(Context context) + { + mContext = context; + } + + public void add(int stringId, OnClickListener listener) + { + mLabels.add(mContext.getResources().getString(stringId)); + mListeners.add(listener); + } + + public void add(CharSequence label, OnClickListener listener) + { + mLabels.add(label); + mListeners.add(listener); + } + + public void applyToBuilder(AlertDialog.Builder builder) + { + CharSequence[] labels = new CharSequence[mLabels.size()]; + labels = mLabels.toArray(labels); + builder.setItems(labels, (dialog, i) -> mListeners.get(i).onClick(dialog, i)); + } +} -- cgit v1.2.3 From f01ccfdb824d37dd9bee2eb150a7721ea19b6837 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 27 Jun 2020 12:35:18 +0200 Subject: Android: Only allow conversion when appropriate --- .../dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java | 10 ++++++++-- .../main/java/org/dolphinemu/dolphinemu/model/GameFile.java | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'Source/Android/app/src/main/java/org') diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java index d3f5395107..6480b24c2c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GamePropertiesDialog.java @@ -29,6 +29,7 @@ public class GamePropertiesDialog extends DialogFragment private static final String ARG_GAMEID = "game_id"; public static final String ARG_REVISION = "revision"; private static final String ARG_PLATFORM = "platform"; + private static final String ARG_SHOULD_ALLOW_CONVERSION = "should_allow_conversion"; public static GamePropertiesDialog newInstance(GameFile gameFile) { @@ -39,6 +40,7 @@ public class GamePropertiesDialog extends DialogFragment arguments.putString(ARG_GAMEID, gameFile.getGameId()); arguments.putInt(ARG_REVISION, gameFile.getRevision()); arguments.putInt(ARG_PLATFORM, gameFile.getPlatform()); + arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion()); fragment.setArguments(arguments); return fragment; @@ -52,6 +54,7 @@ public class GamePropertiesDialog extends DialogFragment String gameId = requireArguments().getString(ARG_GAMEID); int revision = requireArguments().getInt(ARG_REVISION); int platform = requireArguments().getInt(ARG_PLATFORM); + boolean shouldAllowConversion = requireArguments().getBoolean(ARG_SHOULD_ALLOW_CONVERSION); AlertDialogItemsBuilder itemsBuilder = new AlertDialogItemsBuilder(requireContext()); @@ -59,8 +62,11 @@ public class GamePropertiesDialog extends DialogFragment GameDetailsDialog.newInstance(path).show(requireActivity() .getSupportFragmentManager(), "game_details")); - itemsBuilder.add(R.string.properties_convert, (dialog, i) -> - ConvertActivity.launch(getContext(), path)); + if (shouldAllowConversion) + { + itemsBuilder.add(R.string.properties_convert, (dialog, i) -> + ConvertActivity.launch(getContext(), path)); + } itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) -> { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java index c0b55f5da7..9ac331c2b2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java @@ -48,6 +48,8 @@ public class GameFile public native boolean shouldShowFileFormatDetails(); + public native boolean shouldAllowConversion(); + public native long getFileSize(); public native boolean isDatelDisc(); -- cgit v1.2.3 From 5ba083215852a7f1afa2ac73316c2c0c500a6e7d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 12 Sep 2020 13:43:22 +0200 Subject: Android: Add workaround for conversion progress in dark mode --- .../java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Source/Android/app/src/main/java/org') 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 700721fd03..ac1237ba59 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 @@ -409,7 +409,9 @@ public class ConvertFragment extends Fragment implements View.OnClickListener mCanceled = false; - ProgressDialog progressDialog = new ProgressDialog(context, R.style.DolphinDialogBase); + // For some reason, setting R.style.DolphinDialogBase as the theme here gives us white text + // on a white background when the device is set to dark mode, so let's not set a theme. + ProgressDialog progressDialog = new ProgressDialog(context); progressDialog.setTitle(R.string.convert_converting); -- cgit v1.2.3