From 03675f76776e35108f7e39d36d917ea9613fd7a6 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 3 Jun 2023 22:23:30 -0400 Subject: [PATCH 1/3] Android: Convert EmulationActivity to Kotlin --- .../activities/EmulationActivity.java | 1305 ----------------- .../activities/EmulationActivity.kt | 1071 ++++++++++++++ .../riivolution/ui/RiivolutionBootActivity.kt | 2 +- .../fragments/EmulationFragment.java | 3 +- .../dolphinemu/fragments/MenuFragment.java | 2 +- 5 files changed, 1075 insertions(+), 1308 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java deleted file mode 100644 index f43da9bed3..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ /dev/null @@ -1,1305 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.activities; - -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Rect; -import android.os.Build; -import android.os.Bundle; -import android.util.Pair; -import android.util.SparseIntArray; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.LinearLayoutManager; - -import org.dolphinemu.dolphinemu.NativeLibrary; -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding; -import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding; -import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding; -import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig; -import org.dolphinemu.dolphinemu.features.infinitybase.model.Figure; -import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot; -import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter; -import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; -import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.IntSetting; -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.features.skylanders.SkylanderConfig; -import org.dolphinemu.dolphinemu.features.skylanders.model.Skylander; -import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlot; -import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlotAdapter; -import org.dolphinemu.dolphinemu.fragments.EmulationFragment; -import org.dolphinemu.dolphinemu.fragments.MenuFragment; -import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment; -import org.dolphinemu.dolphinemu.overlay.InputOverlay; -import org.dolphinemu.dolphinemu.overlay.InputOverlayPointer; -import org.dolphinemu.dolphinemu.ui.main.MainPresenter; -import org.dolphinemu.dolphinemu.ui.main.ThemeProvider; -import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; -import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; -import org.dolphinemu.dolphinemu.utils.ThemeHelper; - -import java.lang.annotation.Retention; -import java.util.ArrayList; -import java.util.List; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.slider.Slider; - -public final class EmulationActivity extends AppCompatActivity implements ThemeProvider -{ - private static final String BACKSTACK_NAME_MENU = "menu"; - private static final String BACKSTACK_NAME_SUBMENU = "submenu"; - public static final int REQUEST_CHANGE_DISC = 1; - public static final int REQUEST_SKYLANDER_FILE = 2; - public static final int REQUEST_CREATE_SKYLANDER = 3; - public static final int REQUEST_INFINITY_FIGURE_FILE = 4; - public static final int REQUEST_CREATE_INFINITY_FIGURE = 5; - - private EmulationFragment mEmulationFragment; - - private SharedPreferences mPreferences; - - private Settings mSettings; - - private int mThemeId; - - private boolean mMenuVisible; - - private static boolean sIgnoreLaunchRequests = false; - - private boolean activityRecreated; - private String[] mPaths; - private boolean mRiivolution; - private boolean mLaunchSystemMenu; - private static boolean sUserPausedEmulation; - private boolean mMenuToastShown; - - public static final String EXTRA_SELECTED_GAMES = "SelectedGames"; - public static final String EXTRA_RIIVOLUTION = "Riivolution"; - public static final String EXTRA_SYSTEM_MENU = "SystemMenu"; - public static final String EXTRA_USER_PAUSED_EMULATION = "sUserPausedEmulation"; - public static final String EXTRA_MENU_TOAST_SHOWN = "MenuToastShown"; - public static final String EXTRA_SKYLANDER_SLOT = "SkylanderSlot"; - public static final String EXTRA_SKYLANDER_ID = "SkylanderId"; - public static final String EXTRA_SKYLANDER_VAR = "SkylanderVar"; - public static final String EXTRA_SKYLANDER_NAME = "SkylanderName"; - public static final String EXTRA_INFINITY_POSITION = "FigurePosition"; - public static final String EXTRA_INFINITY_LIST_POSITION = "FigureListPosition"; - public static final String EXTRA_INFINITY_NUM = "FigureNum"; - public static final String EXTRA_INFINITY_NAME = "FigureName"; - - @Retention(SOURCE) - @IntDef( - {MENU_ACTION_EDIT_CONTROLS_PLACEMENT, MENU_ACTION_TOGGLE_CONTROLS, MENU_ACTION_ADJUST_SCALE, - MENU_ACTION_CHOOSE_CONTROLLER, MENU_ACTION_REFRESH_WIIMOTES, MENU_ACTION_TAKE_SCREENSHOT, - MENU_ACTION_QUICK_SAVE, MENU_ACTION_QUICK_LOAD, MENU_ACTION_SAVE_ROOT, - MENU_ACTION_LOAD_ROOT, MENU_ACTION_SAVE_SLOT1, MENU_ACTION_SAVE_SLOT2, - MENU_ACTION_SAVE_SLOT3, MENU_ACTION_SAVE_SLOT4, MENU_ACTION_SAVE_SLOT5, - MENU_ACTION_SAVE_SLOT6, MENU_ACTION_LOAD_SLOT1, MENU_ACTION_LOAD_SLOT2, - MENU_ACTION_LOAD_SLOT3, MENU_ACTION_LOAD_SLOT4, MENU_ACTION_LOAD_SLOT5, - MENU_ACTION_LOAD_SLOT6, MENU_ACTION_EXIT, MENU_ACTION_CHANGE_DISC, - MENU_ACTION_RESET_OVERLAY, MENU_SET_IR_RECENTER, MENU_SET_IR_MODE, - MENU_ACTION_CHOOSE_DOUBLETAP, MENU_ACTION_PAUSE_EMULATION, - MENU_ACTION_UNPAUSE_EMULATION, MENU_ACTION_OVERLAY_CONTROLS, MENU_ACTION_SETTINGS, - MENU_ACTION_SKYLANDERS, MENU_ACTION_INFINITY_BASE}) - public @interface MenuAction - { - } - - public static final int MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0; - public static final int MENU_ACTION_TOGGLE_CONTROLS = 1; - public static final int MENU_ACTION_ADJUST_SCALE = 2; - public static final int MENU_ACTION_CHOOSE_CONTROLLER = 3; - public static final int MENU_ACTION_REFRESH_WIIMOTES = 4; - public static final int MENU_ACTION_TAKE_SCREENSHOT = 5; - public static final int MENU_ACTION_QUICK_SAVE = 6; - public static final int MENU_ACTION_QUICK_LOAD = 7; - public static final int MENU_ACTION_SAVE_ROOT = 8; - public static final int MENU_ACTION_LOAD_ROOT = 9; - public static final int MENU_ACTION_SAVE_SLOT1 = 10; - public static final int MENU_ACTION_SAVE_SLOT2 = 11; - public static final int MENU_ACTION_SAVE_SLOT3 = 12; - public static final int MENU_ACTION_SAVE_SLOT4 = 13; - public static final int MENU_ACTION_SAVE_SLOT5 = 14; - public static final int MENU_ACTION_SAVE_SLOT6 = 15; - public static final int MENU_ACTION_LOAD_SLOT1 = 16; - public static final int MENU_ACTION_LOAD_SLOT2 = 17; - public static final int MENU_ACTION_LOAD_SLOT3 = 18; - public static final int MENU_ACTION_LOAD_SLOT4 = 19; - public static final int MENU_ACTION_LOAD_SLOT5 = 20; - public static final int MENU_ACTION_LOAD_SLOT6 = 21; - public static final int MENU_ACTION_EXIT = 22; - public static final int MENU_ACTION_CHANGE_DISC = 23; - public static final int MENU_ACTION_JOYSTICK_REL_CENTER = 24; - public static final int MENU_ACTION_RESET_OVERLAY = 26; - public static final int MENU_SET_IR_RECENTER = 27; - public static final int MENU_SET_IR_MODE = 28; - public static final int MENU_ACTION_CHOOSE_DOUBLETAP = 30; - public static final int MENU_ACTION_PAUSE_EMULATION = 32; - public static final int MENU_ACTION_UNPAUSE_EMULATION = 33; - public static final int MENU_ACTION_OVERLAY_CONTROLS = 34; - public static final int MENU_ACTION_SETTINGS = 35; - public static final int MENU_ACTION_SKYLANDERS = 36; - public static final int MENU_ACTION_INFINITY_BASE = 37; - - private Skylander mSkylanderData = new Skylander(-1, -1, "Slot"); - private Figure mInfinityFigureData = new Figure(-1, "Position"); - - private int mSkylanderSlot = -1; - private int mInfinityPosition = -1; - private int mInfinityListPosition = -1; - - private DialogNfcFiguresManagerBinding mSkylandersBinding; - private DialogNfcFiguresManagerBinding mInfinityBinding; - - private static List sSkylanderSlots = new ArrayList<>(); - private static List sInfinityFigures = new ArrayList<>(); - - private static final SparseIntArray buttonsActionsMap = new SparseIntArray(); - - static - { - buttonsActionsMap.append(R.id.menu_emulation_edit_layout, - EmulationActivity.MENU_ACTION_EDIT_CONTROLS_PLACEMENT); - buttonsActionsMap.append(R.id.menu_emulation_toggle_controls, - EmulationActivity.MENU_ACTION_TOGGLE_CONTROLS); - buttonsActionsMap - .append(R.id.menu_emulation_adjust_scale, EmulationActivity.MENU_ACTION_ADJUST_SCALE); - buttonsActionsMap.append(R.id.menu_emulation_choose_controller, - EmulationActivity.MENU_ACTION_CHOOSE_CONTROLLER); - buttonsActionsMap.append(R.id.menu_emulation_joystick_rel_center, - EmulationActivity.MENU_ACTION_JOYSTICK_REL_CENTER); - buttonsActionsMap - .append(R.id.menu_emulation_reset_overlay, EmulationActivity.MENU_ACTION_RESET_OVERLAY); - buttonsActionsMap.append(R.id.menu_emulation_ir_recenter, - EmulationActivity.MENU_SET_IR_RECENTER); - buttonsActionsMap.append(R.id.menu_emulation_set_ir_mode, - EmulationActivity.MENU_SET_IR_MODE); - buttonsActionsMap.append(R.id.menu_emulation_choose_doubletap, - EmulationActivity.MENU_ACTION_CHOOSE_DOUBLETAP); - } - - public static void launch(FragmentActivity activity, String filePath, boolean riivolution) - { - launch(activity, new String[]{filePath}, riivolution); - } - - private static void performLaunchChecks(FragmentActivity activity, - Runnable continueCallback) - { - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () -> - { - if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) || - !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) || - !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DUMP_PATH) || - !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_LOAD_PATH) || - !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_RESOURCEPACK_PATH)) - { - new MaterialAlertDialogBuilder(activity) - .setMessage(R.string.unavailable_paths) - .setPositiveButton(R.string.yes, - (dialogInterface, i) -> SettingsActivity.launch(activity, - MenuTag.CONFIG_PATHS)) - .setNeutralButton(R.string.continue_anyway, - (dialogInterface, i) -> continueCallback.run()) - .show(); - } - else if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_WII_SD_CARD_IMAGE_PATH) || - !FileBrowserHelper.isPathEmptyOrValid( - StringSetting.MAIN_WII_SD_CARD_SYNC_FOLDER_PATH)) - { - new MaterialAlertDialogBuilder(activity) - .setMessage(R.string.unavailable_paths) - .setPositiveButton(R.string.yes, - (dialogInterface, i) -> SettingsActivity.launch(activity, - MenuTag.CONFIG_WII)) - .setNeutralButton(R.string.continue_anyway, - (dialogInterface, i) -> continueCallback.run()) - .show(); - } - else - { - continueCallback.run(); - } - }); - } - - public static void launchSystemMenu(FragmentActivity activity) - { - if (sIgnoreLaunchRequests) - return; - - performLaunchChecks(activity, () -> - { - launchSystemMenuWithoutChecks(activity); - }); - } - - public static void launch(FragmentActivity activity, String[] filePaths, boolean riivolution) - { - if (sIgnoreLaunchRequests) - return; - - performLaunchChecks(activity, () -> - { - launchWithoutChecks(activity, filePaths, riivolution); - }); - } - - private static void launchSystemMenuWithoutChecks(FragmentActivity activity) - { - sIgnoreLaunchRequests = true; - - Intent launcher = new Intent(activity, EmulationActivity.class); - launcher.putExtra(EmulationActivity.EXTRA_SYSTEM_MENU, true); - activity.startActivity(launcher); - } - - private static void launchWithoutChecks(FragmentActivity activity, String[] filePaths, - boolean riivolution) - { - sIgnoreLaunchRequests = true; - - Intent launcher = new Intent(activity, EmulationActivity.class); - launcher.putExtra(EXTRA_SELECTED_GAMES, filePaths); - launcher.putExtra(EXTRA_RIIVOLUTION, riivolution); - - activity.startActivity(launcher); - } - - public static void stopIgnoringLaunchRequests() - { - sIgnoreLaunchRequests = false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) - { - ThemeHelper.setTheme(this); - - super.onCreate(savedInstanceState); - - MainPresenter.skipRescanningLibrary(); - - if (savedInstanceState == null) - { - // Get params we were passed - Intent gameToEmulate = getIntent(); - mPaths = gameToEmulate.getStringArrayExtra(EXTRA_SELECTED_GAMES); - mRiivolution = gameToEmulate.getBooleanExtra(EXTRA_RIIVOLUTION, false); - mLaunchSystemMenu = gameToEmulate.getBooleanExtra(EXTRA_SYSTEM_MENU, false); - sUserPausedEmulation = gameToEmulate.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false); - mMenuToastShown = false; - activityRecreated = false; - } - else - { - activityRecreated = true; - restoreState(savedInstanceState); - } - - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - mSettings = new Settings(); - mSettings.loadSettings(); - - updateOrientation(); - - // Set these options now so that the SurfaceView the game renders into is the right size. - enableFullscreenImmersive(); - - ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - setInsets(binding.frameMenu); - - // Find or create the EmulationFragment - mEmulationFragment = (EmulationFragment) getSupportFragmentManager() - .findFragmentById(R.id.frame_emulation_fragment); - if (mEmulationFragment == null) - { - mEmulationFragment = EmulationFragment.newInstance(mPaths, mRiivolution, mLaunchSystemMenu); - getSupportFragmentManager().beginTransaction() - .add(R.id.frame_emulation_fragment, mEmulationFragment) - .commit(); - } - - if (NativeLibrary.IsGameMetadataValid()) - setTitle(NativeLibrary.GetCurrentTitleDescription()); - - if (sSkylanderSlots.isEmpty()) - { - for (int i = 0; i < 8; i++) - { - sSkylanderSlots.add(new SkylanderSlot(getString(R.string.skylander_slot, i + 1), i)); - } - } - - if (sInfinityFigures.isEmpty()) - { - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_hexagon_label), 0)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p1_label), 1)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p1a1_label), 3)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p1a2_label), 5)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p2_label), 2)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p2a1_label), 4)); - sInfinityFigures.add(new FigureSlot(getString(R.string.infinity_p2a2_label), 6)); - } - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) - { - if (!isChangingConfigurations()) - { - mEmulationFragment.saveTemporaryState(); - } - outState.putStringArray(EXTRA_SELECTED_GAMES, mPaths); - outState.putBoolean(EXTRA_USER_PAUSED_EMULATION, sUserPausedEmulation); - outState.putBoolean(EXTRA_MENU_TOAST_SHOWN, mMenuToastShown); - outState.putInt(EXTRA_SKYLANDER_SLOT, mSkylanderSlot); - outState.putInt(EXTRA_SKYLANDER_ID, mSkylanderData.getId()); - outState.putInt(EXTRA_SKYLANDER_VAR, mSkylanderData.getVariant()); - outState.putString(EXTRA_SKYLANDER_NAME, mSkylanderData.getName()); - outState.putInt(EXTRA_INFINITY_POSITION, mInfinityPosition); - outState.putInt(EXTRA_INFINITY_LIST_POSITION, mInfinityListPosition); - outState.putLong(EXTRA_INFINITY_NUM, mInfinityFigureData.getNumber()); - outState.putString(EXTRA_INFINITY_NAME, mInfinityFigureData.getName()); - super.onSaveInstanceState(outState); - } - - protected void restoreState(Bundle savedInstanceState) - { - mPaths = savedInstanceState.getStringArray(EXTRA_SELECTED_GAMES); - sUserPausedEmulation = savedInstanceState.getBoolean(EXTRA_USER_PAUSED_EMULATION); - mMenuToastShown = savedInstanceState.getBoolean(EXTRA_MENU_TOAST_SHOWN); - mSkylanderSlot = savedInstanceState.getInt(EXTRA_SKYLANDER_SLOT); - mSkylanderData = new Skylander(savedInstanceState.getInt(EXTRA_SKYLANDER_ID), - savedInstanceState.getInt(EXTRA_SKYLANDER_VAR), - savedInstanceState.getString(EXTRA_SKYLANDER_NAME)); - mInfinityPosition = savedInstanceState.getInt(EXTRA_INFINITY_POSITION); - mInfinityListPosition = savedInstanceState.getInt(EXTRA_INFINITY_LIST_POSITION); - mInfinityFigureData = new Figure(savedInstanceState.getLong(EXTRA_INFINITY_NUM), - savedInstanceState.getString(EXTRA_INFINITY_NAME)); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) - { - if (hasFocus) - { - enableFullscreenImmersive(); - } - } - - @Override - protected void onResume() - { - ThemeHelper.setCorrectTheme(this); - - super.onResume(); - - // Only android 9+ support this feature. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) - { - WindowManager.LayoutParams attributes = getWindow().getAttributes(); - - attributes.layoutInDisplayCutoutMode = - BooleanSetting.MAIN_EXPAND_TO_CUTOUT_AREA.getBoolean() ? - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES : - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; - - getWindow().setAttributes(attributes); - } - - updateOrientation(); - - DolphinSensorEventListener.setDeviceRotation( - getWindowManager().getDefaultDisplay().getRotation()); - } - - @Override - protected void onPause() - { - super.onPause(); - } - - @Override - protected void onStop() - { - super.onStop(); - mSettings.saveSettings(null); - } - - public void onTitleChanged() - { - if (!mMenuToastShown) - { - // The reason why this doesn't run earlier is because we want to be sure the boot succeeded. - Toast.makeText(this, R.string.emulation_menu_help, Toast.LENGTH_LONG).show(); - mMenuToastShown = true; - } - - setTitle(NativeLibrary.GetCurrentTitleDescription()); - - mEmulationFragment.refreshInputOverlay(); - } - - @Override - protected void onDestroy() - { - super.onDestroy(); - mSettings.close(); - } - - @Override - public void onBackPressed() - { - if (!closeSubmenu()) - { - toggleMenu(); - } - } - - @Override - public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) - { - if (keyCode == KeyEvent.KEYCODE_BACK) - { - mEmulationFragment.stopEmulation(); - return true; - } - return super.onKeyLongPress(keyCode, event); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent result) - { - super.onActivityResult(requestCode, resultCode, result); - // If the user picked a file, as opposed to just backing out. - if (resultCode == RESULT_OK) - { - if (requestCode == REQUEST_CHANGE_DISC) - { - NativeLibrary.ChangeDisc(result.getData().toString()); - } - else if (requestCode == REQUEST_SKYLANDER_FILE) - { - Pair slot = - SkylanderConfig.loadSkylander(sSkylanderSlots.get(mSkylanderSlot).getPortalSlot(), - result.getData().toString()); - clearSkylander(mSkylanderSlot); - sSkylanderSlots.get(mSkylanderSlot).setPortalSlot(slot.first); - sSkylanderSlots.get(mSkylanderSlot).setLabel(slot.second); - mSkylandersBinding.figureManager.getAdapter().notifyItemChanged(mSkylanderSlot); - mSkylanderSlot = -1; - mSkylanderData = Skylander.BLANK_SKYLANDER; - } - else if (requestCode == REQUEST_CREATE_SKYLANDER) - { - if (!(mSkylanderData.getId() == -1) && !(mSkylanderData.getVariant() == -1)) - { - Pair slot = SkylanderConfig.createSkylander(mSkylanderData.getId(), - mSkylanderData.getVariant(), - result.getData().toString(), sSkylanderSlots.get(mSkylanderSlot).getPortalSlot()); - clearSkylander(mSkylanderSlot); - sSkylanderSlots.get(mSkylanderSlot).setPortalSlot(slot.first); - sSkylanderSlots.get(mSkylanderSlot).setLabel(slot.second); - mSkylandersBinding.figureManager.getAdapter().notifyItemChanged(mSkylanderSlot); - mSkylanderSlot = -1; - mSkylanderData = Skylander.BLANK_SKYLANDER; - } - } - else if (requestCode == REQUEST_INFINITY_FIGURE_FILE) - { - String label = InfinityConfig.loadFigure(mInfinityPosition, result.getData().toString()); - if (label != null && !label.equals("Unknown Figure")) - { - clearInfinityFigure(mInfinityListPosition); - sInfinityFigures.get(mInfinityListPosition).setLabel(label); - mInfinityBinding.figureManager.getAdapter().notifyItemChanged(mInfinityListPosition); - mInfinityPosition = -1; - mInfinityListPosition = -1; - mInfinityFigureData = Figure.BLANK_FIGURE; - } - else - { - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.incompatible_figure_selected) - .setMessage(R.string.select_compatible_figure) - .setPositiveButton(R.string.ok, null) - .show(); - } - } - else if (requestCode == REQUEST_CREATE_INFINITY_FIGURE) - { - if (!(mInfinityFigureData.getNumber() == -1)) - { - String label = - InfinityConfig.createFigure(mInfinityFigureData.getNumber(), - result.getData().toString(), - mInfinityPosition); - clearInfinityFigure(mInfinityListPosition); - sInfinityFigures.get(mInfinityListPosition).setLabel(label); - mInfinityBinding.figureManager.getAdapter().notifyItemChanged(mInfinityListPosition); - mInfinityPosition = -1; - mInfinityListPosition = -1; - mInfinityFigureData = Figure.BLANK_FIGURE; - } - } - } - } - - private void enableFullscreenImmersive() - { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - private void updateOrientation() - { - setRequestedOrientation(IntSetting.MAIN_EMULATION_ORIENTATION.getInt()); - } - - private boolean closeSubmenu() - { - return getSupportFragmentManager().popBackStackImmediate(BACKSTACK_NAME_SUBMENU, - FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - - private boolean closeMenu() - { - mMenuVisible = false; - return getSupportFragmentManager().popBackStackImmediate(BACKSTACK_NAME_MENU, - FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - - private void toggleMenu() - { - if (!closeMenu()) - { - // Removing the menu failed, so that means it wasn't visible. Add it. - Fragment fragment = MenuFragment.newInstance(); - getSupportFragmentManager().beginTransaction() - .setCustomAnimations( - R.animator.menu_slide_in_from_start, - R.animator.menu_slide_out_to_start, - R.animator.menu_slide_in_from_start, - R.animator.menu_slide_out_to_start) - .add(R.id.frame_menu, fragment) - .addToBackStack(BACKSTACK_NAME_MENU) - .commit(); - mMenuVisible = true; - } - } - - private void setInsets(View view) - { - ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> - { - Insets cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); - ViewGroup.MarginLayoutParams mlpMenu = - (ViewGroup.MarginLayoutParams) v.getLayoutParams(); - int menuWidth = getResources().getDimensionPixelSize(R.dimen.menu_width); - if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) - { - mlpMenu.width = cutInsets.left + menuWidth; - } - else - { - mlpMenu.width = cutInsets.right + menuWidth; - } - NativeLibrary.SetObscuredPixelsTop(cutInsets.top); - NativeLibrary.SetObscuredPixelsLeft(cutInsets.left); - return windowInsets; - }); - } - - public void showOverlayControlsMenu(@NonNull View anchor) - { - PopupMenu popup = new PopupMenu(this, anchor); - Menu menu = popup.getMenu(); - - boolean wii = NativeLibrary.IsEmulatingWii(); - int id = wii ? R.menu.menu_overlay_controls_wii : R.menu.menu_overlay_controls_gc; - popup.getMenuInflater().inflate(id, menu); - - // Populate the switch value for joystick center on touch - menu.findItem(R.id.menu_emulation_joystick_rel_center) - .setChecked(BooleanSetting.MAIN_JOYSTICK_REL_CENTER.getBoolean()); - if (wii) - { - menu.findItem(R.id.menu_emulation_ir_recenter) - .setChecked(BooleanSetting.MAIN_IR_ALWAYS_RECENTER.getBoolean()); - } - - popup.setOnMenuItemClickListener(this::onOptionsItemSelected); - - popup.show(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - int action = buttonsActionsMap.get(item.getItemId(), -1); - if (action >= 0) - { - if (item.isCheckable()) - { - // Need to pass a reference to the item to set the switch state, since it is not done automatically - handleCheckableMenuAction(action, item); - } - else - { - handleMenuAction(action); - } - } - return true; - } - - public void handleCheckableMenuAction(@MenuAction int menuAction, MenuItem item) - { - //noinspection SwitchIntDef - switch (menuAction) - { - case MENU_ACTION_JOYSTICK_REL_CENTER: - item.setChecked(!item.isChecked()); - toggleJoystickRelCenter(item.isChecked()); - break; - case MENU_SET_IR_RECENTER: - item.setChecked(!item.isChecked()); - toggleRecenter(item.isChecked()); - break; - } - } - - public void handleMenuAction(@MenuAction int menuAction) - { - //noinspection SwitchIntDef - switch (menuAction) - { - // Edit the placement of the controls - case MENU_ACTION_EDIT_CONTROLS_PLACEMENT: - editControlsPlacement(); - break; - - // Reset overlay placement - case MENU_ACTION_RESET_OVERLAY: - resetOverlay(); - break; - - // Enable/Disable specific buttons or the entire input overlay. - case MENU_ACTION_TOGGLE_CONTROLS: - toggleControls(); - break; - - // Adjust the scale and opacity of the overlay controls. - case MENU_ACTION_ADJUST_SCALE: - adjustScale(); - break; - - // Change the controller for the input overlay. - case MENU_ACTION_CHOOSE_CONTROLLER: - chooseController(); - break; - - case MENU_ACTION_REFRESH_WIIMOTES: - NativeLibrary.RefreshWiimotes(); - break; - - case MENU_ACTION_PAUSE_EMULATION: - sUserPausedEmulation = true; - NativeLibrary.PauseEmulation(); - break; - - case MENU_ACTION_UNPAUSE_EMULATION: - sUserPausedEmulation = false; - NativeLibrary.UnPauseEmulation(); - break; - - // Screenshot capturing - case MENU_ACTION_TAKE_SCREENSHOT: - NativeLibrary.SaveScreenShot(); - break; - - // Quick save / load - case MENU_ACTION_QUICK_SAVE: - NativeLibrary.SaveState(9, false); - break; - - case MENU_ACTION_QUICK_LOAD: - NativeLibrary.LoadState(9); - break; - - case MENU_ACTION_SAVE_ROOT: - showSubMenu(SaveLoadStateFragment.SaveOrLoad.SAVE); - break; - - case MENU_ACTION_LOAD_ROOT: - showSubMenu(SaveLoadStateFragment.SaveOrLoad.LOAD); - break; - - // Save state slots - case MENU_ACTION_SAVE_SLOT1: - NativeLibrary.SaveState(0, false); - break; - - case MENU_ACTION_SAVE_SLOT2: - NativeLibrary.SaveState(1, false); - break; - - case MENU_ACTION_SAVE_SLOT3: - NativeLibrary.SaveState(2, false); - break; - - case MENU_ACTION_SAVE_SLOT4: - NativeLibrary.SaveState(3, false); - break; - - case MENU_ACTION_SAVE_SLOT5: - NativeLibrary.SaveState(4, false); - break; - - case MENU_ACTION_SAVE_SLOT6: - NativeLibrary.SaveState(5, false); - break; - - // Load state slots - case MENU_ACTION_LOAD_SLOT1: - NativeLibrary.LoadState(0); - break; - - case MENU_ACTION_LOAD_SLOT2: - NativeLibrary.LoadState(1); - break; - - case MENU_ACTION_LOAD_SLOT3: - NativeLibrary.LoadState(2); - break; - - case MENU_ACTION_LOAD_SLOT4: - NativeLibrary.LoadState(3); - break; - - case MENU_ACTION_LOAD_SLOT5: - NativeLibrary.LoadState(4); - break; - - case MENU_ACTION_LOAD_SLOT6: - NativeLibrary.LoadState(5); - break; - - case MENU_ACTION_CHANGE_DISC: - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); - startActivityForResult(intent, REQUEST_CHANGE_DISC); - break; - - case MENU_SET_IR_MODE: - setIRMode(); - break; - - case MENU_ACTION_CHOOSE_DOUBLETAP: - chooseDoubleTapButton(); - break; - - case MENU_ACTION_SETTINGS: - SettingsActivity.launch(this, MenuTag.SETTINGS); - break; - - case MENU_ACTION_SKYLANDERS: - showSkylanderPortalSettings(); - break; - - case MENU_ACTION_INFINITY_BASE: - showInfinityBaseSettings(); - break; - - case MENU_ACTION_EXIT: - mEmulationFragment.stopEmulation(); - break; - } - } - - public static boolean getHasUserPausedEmulation() - { - return sUserPausedEmulation; - } - - private void toggleJoystickRelCenter(boolean state) - { - BooleanSetting.MAIN_JOYSTICK_REL_CENTER.setBoolean(mSettings, state); - } - - private void toggleRecenter(boolean state) - { - BooleanSetting.MAIN_IR_ALWAYS_RECENTER.setBoolean(mSettings, state); - mEmulationFragment.refreshOverlayPointer(); - } - - private void editControlsPlacement() - { - if (mEmulationFragment.isConfiguringControls()) - { - mEmulationFragment.stopConfiguringControls(); - } - else - { - closeSubmenu(); - closeMenu(); - mEmulationFragment.startConfiguringControls(); - } - } - - // Gets button presses - @Override - public boolean dispatchKeyEvent(KeyEvent event) - { - if (!mMenuVisible) - { - if (ControllerInterface.dispatchKeyEvent(event)) - { - return true; - } - } - - return super.dispatchKeyEvent(event); - } - - private void toggleControls() - { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.emulation_toggle_controls); - - int currentController = InputOverlay.getConfiguredControllerType(); - - if (currentController == InputOverlay.OVERLAY_GAMECUBE) - { - boolean[] gcEnabledButtons = new boolean[11]; - String gcSettingBase = "MAIN_BUTTON_TOGGLE_GC_"; - - for (int i = 0; i < gcEnabledButtons.length; i++) - { - gcEnabledButtons[i] = BooleanSetting.valueOf(gcSettingBase + i).getBoolean(); - } - builder.setMultiChoiceItems(R.array.gcpadButtons, gcEnabledButtons, - (dialog, indexSelected, isChecked) -> BooleanSetting - .valueOf(gcSettingBase + indexSelected).setBoolean(mSettings, isChecked)); - } - else if (currentController == InputOverlay.OVERLAY_WIIMOTE_CLASSIC) - { - boolean[] wiiClassicEnabledButtons = new boolean[14]; - String classicSettingBase = "MAIN_BUTTON_TOGGLE_CLASSIC_"; - - for (int i = 0; i < wiiClassicEnabledButtons.length; i++) - { - wiiClassicEnabledButtons[i] = - BooleanSetting.valueOf(classicSettingBase + i).getBoolean(); - } - builder.setMultiChoiceItems(R.array.classicButtons, wiiClassicEnabledButtons, - (dialog, indexSelected, isChecked) -> BooleanSetting - .valueOf(classicSettingBase + indexSelected) - .setBoolean(mSettings, isChecked)); - } - else - { - boolean[] wiiEnabledButtons = new boolean[11]; - String wiiSettingBase = "MAIN_BUTTON_TOGGLE_WII_"; - - for (int i = 0; i < wiiEnabledButtons.length; i++) - { - wiiEnabledButtons[i] = BooleanSetting.valueOf(wiiSettingBase + i).getBoolean(); - } - if (currentController == InputOverlay.OVERLAY_WIIMOTE_NUNCHUK) - { - builder.setMultiChoiceItems(R.array.nunchukButtons, wiiEnabledButtons, - (dialog, indexSelected, isChecked) -> BooleanSetting - .valueOf(wiiSettingBase + indexSelected).setBoolean(mSettings, isChecked)); - } - else - { - builder.setMultiChoiceItems(R.array.wiimoteButtons, wiiEnabledButtons, - (dialog, indexSelected, isChecked) -> BooleanSetting - .valueOf(wiiSettingBase + indexSelected).setBoolean(mSettings, isChecked)); - } - } - - builder.setNeutralButton(R.string.emulation_toggle_all, - (dialogInterface, i) -> mEmulationFragment.toggleInputOverlayVisibility(mSettings)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - mEmulationFragment.refreshInputOverlay()) - .show(); - } - - public void chooseDoubleTapButton() - { - int currentValue = IntSetting.MAIN_DOUBLE_TAP_BUTTON.getInt(); - - int buttonList = InputOverlay.getConfiguredControllerType() == - InputOverlay.OVERLAY_WIIMOTE_CLASSIC ? R.array.doubleTapWithClassic : R.array.doubleTap; - - int checkedItem = -1; - int itemCount = getResources().getStringArray(buttonList).length; - for (int i = 0; i < itemCount; i++) - { - if (InputOverlayPointer.DOUBLE_TAP_OPTIONS.get(i) == currentValue) - { - checkedItem = i; - break; - } - } - - new MaterialAlertDialogBuilder(this) - .setSingleChoiceItems(buttonList, checkedItem, - (DialogInterface dialog, int which) -> IntSetting.MAIN_DOUBLE_TAP_BUTTON.setInt( - mSettings, InputOverlayPointer.DOUBLE_TAP_OPTIONS.get(which))) - .setPositiveButton(R.string.ok, - (dialogInterface, i) -> mEmulationFragment.initInputPointer()) - .show(); - } - - private void adjustScale() - { - DialogInputAdjustBinding dialogBinding = DialogInputAdjustBinding.inflate(getLayoutInflater()); - - final Slider scaleSlider = dialogBinding.inputScaleSlider; - final TextView scaleValue = dialogBinding.inputScaleValue; - scaleSlider.setValueTo(150); - scaleSlider.setValue(IntSetting.MAIN_CONTROL_SCALE.getInt()); - scaleSlider.setStepSize(1); - scaleSlider.addOnChangeListener( - (slider, progress, fromUser) -> scaleValue.setText(((int) progress + 50) + "%")); - scaleValue.setText(((int) scaleSlider.getValue() + 50) + "%"); - - // alpha - final Slider sliderOpacity = dialogBinding.inputOpacitySlider; - final TextView valueOpacity = dialogBinding.inputOpacityValue; - sliderOpacity.setValueTo(100); - sliderOpacity.setValue(IntSetting.MAIN_CONTROL_OPACITY.getInt()); - sliderOpacity.setStepSize(1); - sliderOpacity.addOnChangeListener( - (slider, progress, fromUser) -> valueOpacity.setText(((int) progress) + "%")); - valueOpacity.setText(((int) sliderOpacity.getValue()) + "%"); - - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.emulation_control_adjustments) - .setView(dialogBinding.getRoot()) - .setPositiveButton(R.string.ok, (dialog, which) -> - { - IntSetting.MAIN_CONTROL_SCALE.setInt(mSettings, (int) scaleSlider.getValue()); - IntSetting.MAIN_CONTROL_OPACITY.setInt(mSettings, (int) sliderOpacity.getValue()); - mEmulationFragment.refreshInputOverlay(); - }) - .setNeutralButton(R.string.default_values, (dialog, which) -> - { - IntSetting.MAIN_CONTROL_SCALE.delete(mSettings); - IntSetting.MAIN_CONTROL_OPACITY.delete(mSettings); - mEmulationFragment.refreshInputOverlay(); - }) - .show(); - } - - private void addControllerIfNotNone(List entries, List values, - IntSetting controller, int entry, int value) - { - if (controller.getInt() != 0) - { - entries.add(getString(entry)); - values.add(value); - } - } - - private void chooseController() - { - ArrayList entries = new ArrayList<>(); - ArrayList values = new ArrayList<>(); - - entries.add(getString(R.string.none)); - values.add(-1); - - addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_0, R.string.controller_0, 0); - addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_1, R.string.controller_1, 1); - addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_2, R.string.controller_2, 2); - addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_3, R.string.controller_3, 3); - - if (NativeLibrary.IsEmulatingWii()) - { - addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_0, 4); - addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_1, 5); - addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_2, 6); - addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_3, 7); - } - - IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ? - IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER; - int currentValue = controllerSetting.getInt(); - - int checkedItem = -1; - for (int i = 0; i < values.size(); i++) - { - if (values.get(i) == currentValue) - { - checkedItem = i; - break; - } - } - - final SharedPreferences.Editor editor = mPreferences.edit(); - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.emulation_choose_controller) - .setSingleChoiceItems(entries.toArray(new CharSequence[]{}), checkedItem, - (dialog, indexSelected) -> - controllerSetting.setInt(mSettings, values.get(indexSelected))) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - { - editor.apply(); - mEmulationFragment.refreshInputOverlay(); - }) - .setNeutralButton(R.string.emulation_more_controller_settings, - (dialogInterface, i) -> SettingsActivity.launch(this, MenuTag.SETTINGS)) - .show(); - } - - private void setIRMode() - { - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.emulation_ir_mode) - .setSingleChoiceItems(R.array.irModeEntries, - IntSetting.MAIN_IR_MODE.getInt(), - (dialog, indexSelected) -> - IntSetting.MAIN_IR_MODE.setInt(mSettings, indexSelected)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - mEmulationFragment.refreshOverlayPointer()) - .show(); - } - - private void showSkylanderPortalSettings() - { - mSkylandersBinding = - DialogNfcFiguresManagerBinding.inflate(getLayoutInflater()); - mSkylandersBinding.figureManager.setLayoutManager(new LinearLayoutManager(this)); - - mSkylandersBinding.figureManager.setAdapter( - new SkylanderSlotAdapter(sSkylanderSlots, this)); - - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.skylanders_manager) - .setView(mSkylandersBinding.getRoot()) - .show(); - } - - private void showInfinityBaseSettings() - { - mInfinityBinding = - DialogNfcFiguresManagerBinding.inflate(getLayoutInflater()); - mInfinityBinding.figureManager.setLayoutManager(new LinearLayoutManager(this)); - - mInfinityBinding.figureManager.setAdapter( - new FigureSlotAdapter(sInfinityFigures, this)); - - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.infinity_manager) - .setView(mInfinityBinding.getRoot()) - .show(); - } - - public void setSkylanderData(int id, int var, String name, int slot) - { - mSkylanderData = new Skylander(id, var, name); - mSkylanderSlot = slot; - } - - public void clearSkylander(int slot) - { - sSkylanderSlots.get(slot).setLabel(getString(R.string.skylander_slot, slot + 1)); - mSkylandersBinding.figureManager.getAdapter().notifyItemChanged(slot); - } - - public void setInfinityFigureData(Long num, String name, int position, int listPosition) - { - mInfinityFigureData = new Figure(num, name); - mInfinityPosition = position; - mInfinityListPosition = listPosition; - } - - public void clearInfinityFigure(int position) - { - switch (position) - { - case 0: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_hexagon_label)); - break; - case 1: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p1_label)); - break; - case 2: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p1a1_label)); - break; - case 3: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p1a2_label)); - break; - case 4: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p2_label)); - break; - case 5: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p2a1_label)); - break; - case 6: - sInfinityFigures.get(position).setLabel(getString(R.string.infinity_p2a2_label)); - break; - } - mInfinityBinding.figureManager.getAdapter().notifyItemChanged(position); - } - - private void resetOverlay() - { - new MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.emulation_touch_overlay_reset)) - .setPositiveButton(R.string.yes, - (dialogInterface, i) -> mEmulationFragment.resetInputOverlay()) - .setNegativeButton(R.string.cancel, null) - .show(); - } - - private static boolean areCoordinatesOutside(@Nullable View view, float x, float y) - { - if (view == null) - { - return true; - } - - Rect viewBounds = new Rect(); - view.getGlobalVisibleRect(viewBounds); - return !viewBounds.contains(Math.round(x), Math.round(y)); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) - { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) - { - boolean anyMenuClosed = false; - - Fragment submenu = getSupportFragmentManager().findFragmentById(R.id.frame_submenu); - if (submenu != null && areCoordinatesOutside(submenu.getView(), event.getX(), event.getY())) - { - closeSubmenu(); - submenu = null; - anyMenuClosed = true; - } - - if (submenu == null) - { - Fragment menu = getSupportFragmentManager().findFragmentById(R.id.frame_menu); - if (menu != null && areCoordinatesOutside(menu.getView(), event.getX(), event.getY())) - { - closeMenu(); - anyMenuClosed = true; - } - } - - if (anyMenuClosed) - { - return true; - } - } - - return super.dispatchTouchEvent(event); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent event) - { - if (!mMenuVisible) - { - if (ControllerInterface.dispatchGenericMotionEvent(event)) - { - return true; - } - } - - return super.dispatchGenericMotionEvent(event); - } - - private void showSubMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad) - { - // Get rid of any visible submenu - getSupportFragmentManager().popBackStack( - BACKSTACK_NAME_SUBMENU, FragmentManager.POP_BACK_STACK_INCLUSIVE); - - Fragment fragment = SaveLoadStateFragment.newInstance(saveOrLoad); - getSupportFragmentManager().beginTransaction() - .setCustomAnimations( - R.animator.menu_slide_in_from_end, - R.animator.menu_slide_out_to_end, - R.animator.menu_slide_in_from_end, - R.animator.menu_slide_out_to_end) - .replace(R.id.frame_submenu, fragment) - .addToBackStack(BACKSTACK_NAME_SUBMENU) - .commit(); - } - - public boolean isActivityRecreated() - { - return activityRecreated; - } - - public Settings getSettings() - { - return mSettings; - } - - public void initInputPointer() - { - mEmulationFragment.initInputPointer(); - } - - @Override - public void setTheme(int themeId) - { - super.setTheme(themeId); - this.mThemeId = themeId; - } - - @Override - public int getThemeId() - { - return mThemeId; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt new file mode 100644 index 0000000000..dbe900be39 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.activities + +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.content.Intent +import android.graphics.Rect +import android.os.Build +import android.os.Bundle +import android.util.SparseIntArray +import android.view.KeyEvent +import android.view.MenuItem +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup.MarginLayoutParams +import android.view.WindowManager +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.PopupMenu +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.slider.Slider +import org.dolphinemu.dolphinemu.NativeLibrary +import org.dolphinemu.dolphinemu.R +import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding +import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding +import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding +import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig +import org.dolphinemu.dolphinemu.features.infinitybase.model.Figure +import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot +import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface +import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting +import org.dolphinemu.dolphinemu.features.settings.model.IntSetting +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.features.skylanders.SkylanderConfig +import org.dolphinemu.dolphinemu.features.skylanders.model.Skylander +import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlot +import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlotAdapter +import org.dolphinemu.dolphinemu.fragments.EmulationFragment +import org.dolphinemu.dolphinemu.fragments.MenuFragment +import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment +import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment.SaveOrLoad +import org.dolphinemu.dolphinemu.overlay.InputOverlay +import org.dolphinemu.dolphinemu.overlay.InputOverlayPointer +import org.dolphinemu.dolphinemu.ui.main.MainPresenter +import org.dolphinemu.dolphinemu.ui.main.ThemeProvider +import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner +import org.dolphinemu.dolphinemu.utils.FileBrowserHelper +import org.dolphinemu.dolphinemu.utils.ThemeHelper +import kotlin.math.roundToInt + +class EmulationActivity : AppCompatActivity(), ThemeProvider { + private var emulationFragment: EmulationFragment? = null + + private lateinit var settings: Settings + + private var themeId = 0 + + private var menuVisible = false + + var isActivityRecreated = false + private var paths: Array? = null + private var riivolution = false + private var launchSystemMenu = false + private var menuToastShown = false + + private var skylanderData = Skylander(-1, -1, "Slot") + private var infinityFigureData = Figure(-1, "Position") + private var skylanderSlot = -1 + private var infinityPosition = -1 + private var infinityListPosition = -1 + private lateinit var skylandersBinding: DialogNfcFiguresManagerBinding + private lateinit var infinityBinding: DialogNfcFiguresManagerBinding + + private lateinit var binding: ActivityEmulationBinding + + override fun onCreate(savedInstanceState: Bundle?) { + ThemeHelper.setTheme(this) + + super.onCreate(savedInstanceState) + + MainPresenter.skipRescanningLibrary() + + if (savedInstanceState == null) { + // Get params we were passed + paths = intent.getStringArrayExtra(EXTRA_SELECTED_GAMES) + riivolution = intent.getBooleanExtra(EXTRA_RIIVOLUTION, false) + launchSystemMenu = intent.getBooleanExtra(EXTRA_SYSTEM_MENU, false) + hasUserPausedEmulation = + intent.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false) + menuToastShown = false + isActivityRecreated = false + } else { + isActivityRecreated = true + restoreState(savedInstanceState) + } + + settings = Settings() + settings.loadSettings() + + updateOrientation() + + // Set these options now so that the SurfaceView the game renders into is the right size. + enableFullscreenImmersive() + + binding = ActivityEmulationBinding.inflate(layoutInflater) + setContentView(binding.root) + + setInsets() + + // Find or create the EmulationFragment + emulationFragment = supportFragmentManager + .findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? + if (emulationFragment == null) { + emulationFragment = EmulationFragment.newInstance(paths, riivolution, launchSystemMenu) + supportFragmentManager.beginTransaction() + .add(R.id.frame_emulation_fragment, emulationFragment!!) + .commit() + } + + if (NativeLibrary.IsGameMetadataValid()) + title = NativeLibrary.GetCurrentTitleDescription() + + if (skylanderSlots.isEmpty()) { + for (i in 0..7) { + skylanderSlots.add(SkylanderSlot(getString(R.string.skylander_slot, i + 1), i)) + } + } + + if (infinityFigures.isEmpty()) { + infinityFigures.apply { + add(FigureSlot(getString(R.string.infinity_hexagon_label), 0)) + add(FigureSlot(getString(R.string.infinity_p1_label), 1)) + add(FigureSlot(getString(R.string.infinity_p1a1_label), 3)) + add(FigureSlot(getString(R.string.infinity_p1a2_label), 5)) + add(FigureSlot(getString(R.string.infinity_p2_label), 2)) + add(FigureSlot(getString(R.string.infinity_p2a1_label), 4)) + add(FigureSlot(getString(R.string.infinity_p2a2_label), 6)) + } + } + } + + override fun onSaveInstanceState(outState: Bundle) { + if (!isChangingConfigurations) { + emulationFragment!!.saveTemporaryState() + } + + outState.apply { + putStringArray(EXTRA_SELECTED_GAMES, paths) + putBoolean(EXTRA_USER_PAUSED_EMULATION, hasUserPausedEmulation) + putBoolean(EXTRA_MENU_TOAST_SHOWN, menuToastShown) + putInt(EXTRA_SKYLANDER_SLOT, skylanderSlot) + putInt(EXTRA_SKYLANDER_ID, skylanderData.id) + putInt(EXTRA_SKYLANDER_VAR, skylanderData.variant) + putString(EXTRA_SKYLANDER_NAME, skylanderData.name) + putInt(EXTRA_INFINITY_POSITION, infinityPosition) + putInt(EXTRA_INFINITY_LIST_POSITION, infinityListPosition) + putLong(EXTRA_INFINITY_NUM, infinityFigureData.number) + putString(EXTRA_INFINITY_NAME, infinityFigureData.name) + } + super.onSaveInstanceState(outState) + } + + fun restoreState(savedInstanceState: Bundle) { + savedInstanceState.apply { + paths = getStringArray(EXTRA_SELECTED_GAMES) + hasUserPausedEmulation = getBoolean(EXTRA_USER_PAUSED_EMULATION) + menuToastShown = savedInstanceState.getBoolean(EXTRA_MENU_TOAST_SHOWN) + skylanderSlot = savedInstanceState.getInt(EXTRA_SKYLANDER_SLOT) + skylanderData = Skylander( + savedInstanceState.getInt(EXTRA_SKYLANDER_ID), + savedInstanceState.getInt(EXTRA_SKYLANDER_VAR), + savedInstanceState.getString(EXTRA_SKYLANDER_NAME)!! + ) + infinityPosition = savedInstanceState.getInt(EXTRA_INFINITY_POSITION) + infinityListPosition = savedInstanceState.getInt(EXTRA_INFINITY_LIST_POSITION) + infinityFigureData = Figure( + savedInstanceState.getLong(EXTRA_INFINITY_NUM), + savedInstanceState.getString(EXTRA_INFINITY_NAME)!! + ) + } + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + if (hasFocus) { + enableFullscreenImmersive() + } + } + + override fun onResume() { + ThemeHelper.setCorrectTheme(this) + + super.onResume() + + // Only android 9+ support this feature. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val attributes = window.attributes + + attributes.layoutInDisplayCutoutMode = + if (BooleanSetting.MAIN_EXPAND_TO_CUTOUT_AREA.boolean) { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } else { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + } + + window.attributes = attributes + } + + updateOrientation() + + DolphinSensorEventListener.setDeviceRotation(windowManager.defaultDisplay.rotation) + } + + override fun onStop() { + super.onStop() + settings.saveSettings(null) + } + + fun onTitleChanged() { + if (!menuToastShown) { + // The reason why this doesn't run earlier is because we want to be sure the boot succeeded. + Toast.makeText(this, R.string.emulation_menu_help, Toast.LENGTH_LONG).show() + menuToastShown = true + } + + title = NativeLibrary.GetCurrentTitleDescription() + + emulationFragment?.refreshInputOverlay() + } + + override fun onDestroy() { + super.onDestroy() + settings.close() + } + + override fun onBackPressed() { + if (!closeSubmenu()) { + toggleMenu() + } + } + + override fun onKeyLongPress(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK) { + emulationFragment!!.stopEmulation() + return true + } + return super.onKeyLongPress(keyCode, event) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) { + super.onActivityResult(requestCode, resultCode, result) + // If the user picked a file, as opposed to just backing out. + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CHANGE_DISC) { + NativeLibrary.ChangeDisc(result!!.data.toString()) + } else if (requestCode == REQUEST_SKYLANDER_FILE) { + val slot = SkylanderConfig.loadSkylander( + skylanderSlots[skylanderSlot].portalSlot, + result!!.data.toString() + )!! + clearSkylander(skylanderSlot) + skylanderSlots[skylanderSlot].portalSlot = slot.first!! + skylanderSlots[skylanderSlot].label = slot.second!! + skylandersBinding.figureManager.adapter!!.notifyItemChanged(skylanderSlot) + skylanderSlot = -1 + skylanderData = Skylander.BLANK_SKYLANDER + } else if (requestCode == REQUEST_CREATE_SKYLANDER) { + if (skylanderData.id != -1 && skylanderData.variant != -1) { + val slot = SkylanderConfig.createSkylander( + skylanderData.id, + skylanderData.variant, + result!!.data.toString(), + skylanderSlots[skylanderSlot].portalSlot + ) + clearSkylander(skylanderSlot) + skylanderSlots[skylanderSlot].portalSlot = slot.first + skylanderSlots[skylanderSlot].label = slot.second + skylandersBinding.figureManager.adapter?.notifyItemChanged(skylanderSlot) + skylanderSlot = -1 + skylanderData = Skylander.BLANK_SKYLANDER + } + } else if (requestCode == REQUEST_INFINITY_FIGURE_FILE) { + val label = InfinityConfig.loadFigure(infinityPosition, result!!.data.toString()) + if (label != null && label != "Unknown Figure") { + clearInfinityFigure(infinityListPosition) + infinityFigures[infinityListPosition].label = label + infinityBinding.figureManager.adapter?.notifyItemChanged(infinityListPosition) + infinityPosition = -1 + infinityListPosition = -1 + infinityFigureData = Figure.BLANK_FIGURE + } else { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.incompatible_figure_selected) + .setMessage(R.string.select_compatible_figure) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } else if (requestCode == REQUEST_CREATE_INFINITY_FIGURE) { + if (infinityFigureData.number != -1L) { + val label = InfinityConfig.createFigure( + infinityFigureData.number, + result!!.data.toString(), + infinityPosition + ) + clearInfinityFigure(infinityListPosition) + infinityFigures[infinityListPosition].label = label!! + infinityBinding.figureManager.adapter?.notifyItemChanged(infinityListPosition) + infinityPosition = -1 + infinityListPosition = -1 + infinityFigureData = Figure.BLANK_FIGURE + } + } + } + } + + private fun enableFullscreenImmersive() { + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + + private fun updateOrientation() { + requestedOrientation = IntSetting.MAIN_EMULATION_ORIENTATION.int + } + + private fun closeSubmenu(): Boolean = + supportFragmentManager.popBackStackImmediate( + BACKSTACK_NAME_SUBMENU, + FragmentManager.POP_BACK_STACK_INCLUSIVE + ) + + private fun closeMenu(): Boolean { + menuVisible = false + return supportFragmentManager.popBackStackImmediate( + BACKSTACK_NAME_MENU, + FragmentManager.POP_BACK_STACK_INCLUSIVE + ) + } + + private fun toggleMenu() { + if (!closeMenu()) { + // Removing the menu failed, so that means it wasn't visible. Add it. + val fragment: Fragment = MenuFragment.newInstance() + supportFragmentManager.beginTransaction() + .setCustomAnimations( + R.animator.menu_slide_in_from_start, + R.animator.menu_slide_out_to_start, + R.animator.menu_slide_in_from_start, + R.animator.menu_slide_out_to_start + ) + .add(R.id.frame_menu, fragment) + .addToBackStack(BACKSTACK_NAME_MENU) + .commit() + menuVisible = true + } + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener(binding.frameMenu) { v: View, windowInsets: WindowInsetsCompat -> + val cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + val mlpMenu = v.layoutParams as MarginLayoutParams + val menuWidth = resources.getDimensionPixelSize(R.dimen.menu_width) + if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { + mlpMenu.width = cutInsets.left + menuWidth + } else { + mlpMenu.width = cutInsets.right + menuWidth + } + NativeLibrary.SetObscuredPixelsTop(cutInsets.top) + NativeLibrary.SetObscuredPixelsLeft(cutInsets.left) + windowInsets + } + + fun showOverlayControlsMenu(anchor: View) { + val popup = PopupMenu(this, anchor) + val menu = popup.menu + val wii = NativeLibrary.IsEmulatingWii() + val id = if (wii) R.menu.menu_overlay_controls_wii else R.menu.menu_overlay_controls_gc + popup.menuInflater.inflate(id, menu) + + // Populate the switch value for joystick center on touch + menu.findItem(R.id.menu_emulation_joystick_rel_center).isChecked = + BooleanSetting.MAIN_JOYSTICK_REL_CENTER.boolean + if (wii) { + menu.findItem(R.id.menu_emulation_ir_recenter).isChecked = + BooleanSetting.MAIN_IR_ALWAYS_RECENTER.boolean + } + popup.setOnMenuItemClickListener { item: MenuItem -> onOptionsItemSelected(item) } + popup.show() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val action = buttonsActionsMap[item.itemId, -1] + if (action >= 0) { + if (item.isCheckable) { + // Need to pass a reference to the item to set the switch state, since it is not done automatically + handleCheckableMenuAction(action, item) + } else { + handleMenuAction(action) + } + } + return true + } + + private fun handleCheckableMenuAction(menuAction: Int, item: MenuItem) { + when (menuAction) { + MENU_ACTION_JOYSTICK_REL_CENTER -> { + item.isChecked = !item.isChecked + toggleJoystickRelCenter(item.isChecked) + } + + MENU_SET_IR_RECENTER -> { + item.isChecked = !item.isChecked + toggleRecenter(item.isChecked) + } + } + } + + fun handleMenuAction(menuAction: Int) { + when (menuAction) { + MENU_ACTION_EDIT_CONTROLS_PLACEMENT -> editControlsPlacement() + MENU_ACTION_RESET_OVERLAY -> resetOverlay() + MENU_ACTION_TOGGLE_CONTROLS -> toggleControls() + MENU_ACTION_ADJUST_SCALE -> adjustScale() + MENU_ACTION_CHOOSE_CONTROLLER -> chooseController() + MENU_ACTION_REFRESH_WIIMOTES -> NativeLibrary.RefreshWiimotes() + MENU_ACTION_PAUSE_EMULATION -> { + hasUserPausedEmulation = true + NativeLibrary.PauseEmulation() + } + + MENU_ACTION_UNPAUSE_EMULATION -> { + hasUserPausedEmulation = false + NativeLibrary.UnPauseEmulation() + } + + MENU_ACTION_TAKE_SCREENSHOT -> NativeLibrary.SaveScreenShot() + MENU_ACTION_QUICK_SAVE -> NativeLibrary.SaveState(9, false) + MENU_ACTION_QUICK_LOAD -> NativeLibrary.LoadState(9) + MENU_ACTION_SAVE_ROOT -> showSubMenu(SaveOrLoad.SAVE) + MENU_ACTION_LOAD_ROOT -> showSubMenu(SaveOrLoad.LOAD) + MENU_ACTION_SAVE_SLOT1 -> NativeLibrary.SaveState(0, false) + MENU_ACTION_SAVE_SLOT2 -> NativeLibrary.SaveState(1, false) + MENU_ACTION_SAVE_SLOT3 -> NativeLibrary.SaveState(2, false) + MENU_ACTION_SAVE_SLOT4 -> NativeLibrary.SaveState(3, false) + MENU_ACTION_SAVE_SLOT5 -> NativeLibrary.SaveState(4, false) + MENU_ACTION_SAVE_SLOT6 -> NativeLibrary.SaveState(5, false) + MENU_ACTION_LOAD_SLOT1 -> NativeLibrary.LoadState(0) + MENU_ACTION_LOAD_SLOT2 -> NativeLibrary.LoadState(1) + MENU_ACTION_LOAD_SLOT3 -> NativeLibrary.LoadState(2) + MENU_ACTION_LOAD_SLOT4 -> NativeLibrary.LoadState(3) + MENU_ACTION_LOAD_SLOT5 -> NativeLibrary.LoadState(4) + MENU_ACTION_LOAD_SLOT6 -> NativeLibrary.LoadState(5) + MENU_ACTION_CHANGE_DISC -> { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "*/*" + startActivityForResult(intent, REQUEST_CHANGE_DISC) + } + + MENU_SET_IR_MODE -> setIRMode() + MENU_ACTION_CHOOSE_DOUBLETAP -> chooseDoubleTapButton() + MENU_ACTION_SETTINGS -> SettingsActivity.launch(this, MenuTag.SETTINGS) + MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings() + MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings() + MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation() + } + } + + private fun toggleJoystickRelCenter(state: Boolean) { + BooleanSetting.MAIN_JOYSTICK_REL_CENTER.setBoolean(settings, state) + } + + private fun toggleRecenter(state: Boolean) { + BooleanSetting.MAIN_IR_ALWAYS_RECENTER.setBoolean(settings, state) + emulationFragment?.refreshOverlayPointer() + } + + private fun editControlsPlacement() { + if (emulationFragment!!.isConfiguringControls) { + emulationFragment?.stopConfiguringControls() + } else { + closeSubmenu() + closeMenu() + emulationFragment?.startConfiguringControls() + } + } + + // Gets button presses + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + if (!menuVisible) { + if (ControllerInterface.dispatchKeyEvent(event)) { + return true + } + } + return super.dispatchKeyEvent(event) + } + + private fun toggleControls() { + val builder = MaterialAlertDialogBuilder(this) + .setTitle(R.string.emulation_toggle_controls) + + val currentController = InputOverlay.getConfiguredControllerType() + + if (currentController == InputOverlay.OVERLAY_GAMECUBE) { + val gcEnabledButtons = BooleanArray(11) + val gcSettingBase = "MAIN_BUTTON_TOGGLE_GC_" + + for (i in gcEnabledButtons.indices) { + gcEnabledButtons[i] = BooleanSetting.valueOf(gcSettingBase + i).boolean + } + builder.setMultiChoiceItems( + R.array.gcpadButtons, gcEnabledButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting + .valueOf(gcSettingBase + indexSelected).setBoolean(settings, isChecked) + } + } else if (currentController == InputOverlay.OVERLAY_WIIMOTE_CLASSIC) { + val wiiClassicEnabledButtons = BooleanArray(14) + val classicSettingBase = "MAIN_BUTTON_TOGGLE_CLASSIC_" + + for (i in wiiClassicEnabledButtons.indices) { + wiiClassicEnabledButtons[i] = BooleanSetting.valueOf(classicSettingBase + i).boolean + } + builder.setMultiChoiceItems( + R.array.classicButtons, wiiClassicEnabledButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting.valueOf(classicSettingBase + indexSelected) + .setBoolean(settings, isChecked) + } + } else { + val wiiEnabledButtons = BooleanArray(11) + val wiiSettingBase = "MAIN_BUTTON_TOGGLE_WII_" + + for (i in wiiEnabledButtons.indices) { + wiiEnabledButtons[i] = BooleanSetting.valueOf(wiiSettingBase + i).boolean + } + if (currentController == InputOverlay.OVERLAY_WIIMOTE_NUNCHUK) { + builder.setMultiChoiceItems( + R.array.nunchukButtons, wiiEnabledButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting + .valueOf(wiiSettingBase + indexSelected).setBoolean(settings, isChecked) + } + } else { + builder.setMultiChoiceItems( + R.array.wiimoteButtons, wiiEnabledButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting + .valueOf(wiiSettingBase + indexSelected).setBoolean(settings, isChecked) + } + } + } + + builder + .setNeutralButton(R.string.emulation_toggle_all) { _: DialogInterface?, _: Int -> + emulationFragment!!.toggleInputOverlayVisibility(settings) + } + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> emulationFragment?.refreshInputOverlay() } + .show() + } + + private fun chooseDoubleTapButton() { + val currentValue = IntSetting.MAIN_DOUBLE_TAP_BUTTON.int + + val buttonList = + if (InputOverlay.getConfiguredControllerType() == InputOverlay.OVERLAY_WIIMOTE_CLASSIC) R.array.doubleTapWithClassic else R.array.doubleTap + + var checkedItem = -1 + val itemCount = resources.getStringArray(buttonList).size + for (i in 0 until itemCount) { + if (InputOverlayPointer.DOUBLE_TAP_OPTIONS[i] == currentValue) { + checkedItem = i + break + } + } + + MaterialAlertDialogBuilder(this) + .setSingleChoiceItems(buttonList, checkedItem) { _: DialogInterface?, which: Int -> + IntSetting.MAIN_DOUBLE_TAP_BUTTON.setInt( + settings, + InputOverlayPointer.DOUBLE_TAP_OPTIONS[which] + ) + } + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> + emulationFragment?.initInputPointer() + } + .show() + } + + private fun adjustScale() { + val dialogBinding = DialogInputAdjustBinding.inflate(layoutInflater) + dialogBinding.apply { + inputScaleSlider.apply { + valueTo = 150f + value = IntSetting.MAIN_CONTROL_SCALE.int.toFloat() + stepSize = 1f + addOnChangeListener(Slider.OnChangeListener { _: Slider?, progress: Float, _: Boolean -> + dialogBinding.inputScaleValue.text = "${(progress.toInt() + 50)}%" + }) + } + inputScaleValue.text = + "${(dialogBinding.inputScaleSlider.value.toInt() + 50)}%" + + inputOpacitySlider.apply { + valueTo = 100f + value = IntSetting.MAIN_CONTROL_OPACITY.int.toFloat() + stepSize = 1f + addOnChangeListener(Slider.OnChangeListener { _: Slider?, progress: Float, _: Boolean -> + inputOpacityValue.text = progress.toInt().toString() + "%" + }) + } + inputOpacityValue.text = inputOpacitySlider.value.toInt().toString() + "%" + } + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.emulation_control_adjustments) + .setView(dialogBinding.root) + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> + IntSetting.MAIN_CONTROL_SCALE.setInt( + settings, + dialogBinding.inputScaleSlider.value.toInt() + ) + IntSetting.MAIN_CONTROL_OPACITY.setInt( + settings, + dialogBinding.inputOpacitySlider.value.toInt() + ) + emulationFragment?.refreshInputOverlay() + } + .setNeutralButton(R.string.default_values) { _: DialogInterface?, _: Int -> + IntSetting.MAIN_CONTROL_SCALE.delete(settings) + IntSetting.MAIN_CONTROL_OPACITY.delete(settings) + emulationFragment?.refreshInputOverlay() + } + .show() + } + + private fun addControllerIfNotNone( + entries: MutableList, + values: MutableList, + controller: IntSetting, + entry: Int, + value: Int + ) { + if (controller.int != 0) { + entries.add(getString(entry)) + values.add(value) + } + } + + private fun chooseController() { + val entries = ArrayList() + val values = ArrayList() + + entries.add(getString(R.string.none)) + values.add(-1) + + addControllerIfNotNone( + entries, + values, + IntSetting.MAIN_SI_DEVICE_0, + R.string.controller_0, + 0 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.MAIN_SI_DEVICE_1, + R.string.controller_1, + 1 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.MAIN_SI_DEVICE_2, + R.string.controller_2, + 2 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.MAIN_SI_DEVICE_3, + R.string.controller_3, + 3 + ) + + if (NativeLibrary.IsEmulatingWii()) { + addControllerIfNotNone( + entries, + values, + IntSetting.WIIMOTE_1_SOURCE, + R.string.wiimote_0, + 4 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.WIIMOTE_2_SOURCE, + R.string.wiimote_1, + 5 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.WIIMOTE_3_SOURCE, + R.string.wiimote_2, + 6 + ) + addControllerIfNotNone( + entries, + values, + IntSetting.WIIMOTE_4_SOURCE, + R.string.wiimote_3, + 7 + ) + } + + val controllerSetting = + if (NativeLibrary.IsEmulatingWii()) IntSetting.MAIN_OVERLAY_WII_CONTROLLER else IntSetting.MAIN_OVERLAY_GC_CONTROLLER + val currentValue = controllerSetting.int + var checkedItem = -1 + for (i in values.indices) { + if (values[i] == currentValue) { + checkedItem = i + break + } + } + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.emulation_choose_controller) + .setSingleChoiceItems( + entries.toArray(arrayOf()), checkedItem + ) { _: DialogInterface?, indexSelected: Int -> + controllerSetting.setInt(settings, values[indexSelected]) + } + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> + emulationFragment?.refreshInputOverlay() + } + .setNeutralButton( + R.string.emulation_more_controller_settings + ) { _: DialogInterface?, _: Int -> SettingsActivity.launch(this, MenuTag.SETTINGS) } + .show() + } + + private fun setIRMode() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.emulation_ir_mode) + .setSingleChoiceItems( + R.array.irModeEntries, + IntSetting.MAIN_IR_MODE.int + ) { _: DialogInterface?, indexSelected: Int -> + IntSetting.MAIN_IR_MODE.setInt(settings, indexSelected) + } + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> + emulationFragment?.refreshOverlayPointer() + } + .show() + } + + private fun showSkylanderPortalSettings() { + skylandersBinding = DialogNfcFiguresManagerBinding.inflate(layoutInflater) + skylandersBinding.figureManager.apply { + layoutManager = LinearLayoutManager(this@EmulationActivity) + adapter = SkylanderSlotAdapter(skylanderSlots, this@EmulationActivity) + } + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.skylanders_manager) + .setView(skylandersBinding.root) + .show() + } + + private fun showInfinityBaseSettings() { + infinityBinding = DialogNfcFiguresManagerBinding.inflate(layoutInflater) + infinityBinding.figureManager.apply { + layoutManager = LinearLayoutManager(this@EmulationActivity) + adapter = FigureSlotAdapter(infinityFigures, this@EmulationActivity) + } + + MaterialAlertDialogBuilder(this) + .setTitle(R.string.infinity_manager) + .setView(infinityBinding.root) + .show() + } + + fun setSkylanderData(id: Int, variant: Int, name: String, slot: Int) { + skylanderData = Skylander(id, variant, name) + skylanderSlot = slot + } + + fun clearSkylander(slot: Int) { + skylanderSlots[slot].label = getString(R.string.skylander_slot, slot + 1) + skylandersBinding.figureManager.adapter?.notifyItemChanged(slot) + } + + fun setInfinityFigureData(num: Long, name: String, position: Int, listPosition: Int) { + infinityFigureData = Figure(num, name) + infinityPosition = position + infinityListPosition = listPosition + } + + fun clearInfinityFigure(position: Int) { + when (position) { + 0 -> infinityFigures[position].label = getString(R.string.infinity_hexagon_label) + 1 -> infinityFigures[position].label = getString(R.string.infinity_p1_label) + 2 -> infinityFigures[position].label = getString(R.string.infinity_p1a1_label) + 3 -> infinityFigures[position].label = getString(R.string.infinity_p1a2_label) + 4 -> infinityFigures[position].label = getString(R.string.infinity_p2_label) + 5 -> infinityFigures[position].label = getString(R.string.infinity_p2a1_label) + 6 -> infinityFigures[position].label = getString(R.string.infinity_p2a2_label) + } + infinityBinding.figureManager.adapter?.notifyItemChanged(position) + } + + private fun resetOverlay() { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.emulation_touch_overlay_reset)) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + emulationFragment?.resetInputOverlay() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + var anyMenuClosed = false + + var submenu = supportFragmentManager.findFragmentById(R.id.frame_submenu) + if (submenu != null && areCoordinatesOutside(submenu.view, event.x, event.y)) { + closeSubmenu() + submenu = null + anyMenuClosed = true + } + + if (submenu == null) { + val menu = supportFragmentManager.findFragmentById(R.id.frame_menu) + if (menu != null && areCoordinatesOutside(menu.view, event.x, event.y)) { + closeMenu() + anyMenuClosed = true + } + } + + if (anyMenuClosed) + return true + } + return super.dispatchTouchEvent(event) + } + + override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { + if (!menuVisible) { + if (ControllerInterface.dispatchGenericMotionEvent(event)) { + return true + } + } + return super.dispatchGenericMotionEvent(event) + } + + private fun showSubMenu(saveOrLoad: SaveOrLoad) { + // Get rid of any visible submenu + supportFragmentManager.popBackStack( + BACKSTACK_NAME_SUBMENU, + FragmentManager.POP_BACK_STACK_INCLUSIVE + ) + + val fragment: Fragment = SaveLoadStateFragment.newInstance(saveOrLoad) + supportFragmentManager.beginTransaction() + .setCustomAnimations( + R.animator.menu_slide_in_from_end, + R.animator.menu_slide_out_to_end, + R.animator.menu_slide_in_from_end, + R.animator.menu_slide_out_to_end + ) + .replace(R.id.frame_submenu, fragment) + .addToBackStack(BACKSTACK_NAME_SUBMENU) + .commit() + } + + fun initInputPointer() { + emulationFragment?.initInputPointer() + } + + override fun setTheme(themeId: Int) { + super.setTheme(themeId) + this.themeId = themeId + } + + override fun getThemeId(): Int = themeId + + companion object { + private const val BACKSTACK_NAME_MENU = "menu" + private const val BACKSTACK_NAME_SUBMENU = "submenu" + const val REQUEST_CHANGE_DISC = 1 + const val REQUEST_SKYLANDER_FILE = 2 + const val REQUEST_CREATE_SKYLANDER = 3 + const val REQUEST_INFINITY_FIGURE_FILE = 4 + const val REQUEST_CREATE_INFINITY_FIGURE = 5 + + private var ignoreLaunchRequests = false + + var hasUserPausedEmulation = false + + private val skylanderSlots: MutableList = ArrayList() + private val infinityFigures: MutableList = ArrayList() + private val buttonsActionsMap = SparseIntArray() + + const val EXTRA_SELECTED_GAMES = "SelectedGames" + const val EXTRA_RIIVOLUTION = "Riivolution" + const val EXTRA_SYSTEM_MENU = "SystemMenu" + const val EXTRA_USER_PAUSED_EMULATION = "sUserPausedEmulation" + const val EXTRA_MENU_TOAST_SHOWN = "MenuToastShown" + const val EXTRA_SKYLANDER_SLOT = "SkylanderSlot" + const val EXTRA_SKYLANDER_ID = "SkylanderId" + const val EXTRA_SKYLANDER_VAR = "SkylanderVar" + const val EXTRA_SKYLANDER_NAME = "SkylanderName" + const val EXTRA_INFINITY_POSITION = "FigurePosition" + const val EXTRA_INFINITY_LIST_POSITION = "FigureListPosition" + const val EXTRA_INFINITY_NUM = "FigureNum" + const val EXTRA_INFINITY_NAME = "FigureName" + const val MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0 + const val MENU_ACTION_TOGGLE_CONTROLS = 1 + const val MENU_ACTION_ADJUST_SCALE = 2 + const val MENU_ACTION_CHOOSE_CONTROLLER = 3 + const val MENU_ACTION_REFRESH_WIIMOTES = 4 + const val MENU_ACTION_TAKE_SCREENSHOT = 5 + const val MENU_ACTION_QUICK_SAVE = 6 + const val MENU_ACTION_QUICK_LOAD = 7 + const val MENU_ACTION_SAVE_ROOT = 8 + const val MENU_ACTION_LOAD_ROOT = 9 + const val MENU_ACTION_SAVE_SLOT1 = 10 + const val MENU_ACTION_SAVE_SLOT2 = 11 + const val MENU_ACTION_SAVE_SLOT3 = 12 + const val MENU_ACTION_SAVE_SLOT4 = 13 + const val MENU_ACTION_SAVE_SLOT5 = 14 + const val MENU_ACTION_SAVE_SLOT6 = 15 + const val MENU_ACTION_LOAD_SLOT1 = 16 + const val MENU_ACTION_LOAD_SLOT2 = 17 + const val MENU_ACTION_LOAD_SLOT3 = 18 + const val MENU_ACTION_LOAD_SLOT4 = 19 + const val MENU_ACTION_LOAD_SLOT5 = 20 + const val MENU_ACTION_LOAD_SLOT6 = 21 + const val MENU_ACTION_EXIT = 22 + const val MENU_ACTION_CHANGE_DISC = 23 + const val MENU_ACTION_JOYSTICK_REL_CENTER = 24 + const val MENU_ACTION_RESET_OVERLAY = 26 + const val MENU_SET_IR_RECENTER = 27 + const val MENU_SET_IR_MODE = 28 + const val MENU_ACTION_CHOOSE_DOUBLETAP = 30 + const val MENU_ACTION_PAUSE_EMULATION = 32 + const val MENU_ACTION_UNPAUSE_EMULATION = 33 + const val MENU_ACTION_OVERLAY_CONTROLS = 34 + const val MENU_ACTION_SETTINGS = 35 + const val MENU_ACTION_SKYLANDERS = 36 + const val MENU_ACTION_INFINITY_BASE = 37 + + init { + buttonsActionsMap.apply { + append(R.id.menu_emulation_edit_layout, MENU_ACTION_EDIT_CONTROLS_PLACEMENT) + append(R.id.menu_emulation_toggle_controls, MENU_ACTION_TOGGLE_CONTROLS) + append(R.id.menu_emulation_adjust_scale, MENU_ACTION_ADJUST_SCALE) + append(R.id.menu_emulation_choose_controller, MENU_ACTION_CHOOSE_CONTROLLER) + append(R.id.menu_emulation_joystick_rel_center, MENU_ACTION_JOYSTICK_REL_CENTER) + append(R.id.menu_emulation_reset_overlay, MENU_ACTION_RESET_OVERLAY) + append(R.id.menu_emulation_ir_recenter, MENU_SET_IR_RECENTER) + append(R.id.menu_emulation_set_ir_mode, MENU_SET_IR_MODE) + append(R.id.menu_emulation_choose_doubletap, MENU_ACTION_CHOOSE_DOUBLETAP) + } + } + + @JvmStatic + fun launch(activity: FragmentActivity, filePaths: Array, riivolution: Boolean) { + if (ignoreLaunchRequests) + return + + performLaunchChecks(activity) { launchWithoutChecks(activity, filePaths, riivolution) } + } + + @JvmStatic + fun launch(activity: FragmentActivity, filePath: String, riivolution: Boolean) = + launch(activity, arrayOf(filePath), riivolution) + + private fun launchWithoutChecks( + activity: FragmentActivity, + filePaths: Array, + riivolution: Boolean + ) { + ignoreLaunchRequests = true + val launcher = Intent(activity, EmulationActivity::class.java) + launcher.putExtra(EXTRA_SELECTED_GAMES, filePaths) + launcher.putExtra(EXTRA_RIIVOLUTION, riivolution) + activity.startActivity(launcher) + } + + private fun performLaunchChecks(activity: FragmentActivity, continueCallback: Runnable) { + AfterDirectoryInitializationRunner().runWithLifecycle(activity) { + if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) || + !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) || + !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DUMP_PATH) || + !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_LOAD_PATH) || + !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_RESOURCEPACK_PATH) + ) { + MaterialAlertDialogBuilder(activity) + .setMessage(R.string.unavailable_paths) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + SettingsActivity.launch(activity, MenuTag.CONFIG_PATHS) + } + .setNeutralButton(R.string.continue_anyway) { _: DialogInterface?, _: Int -> + continueCallback.run() + } + .show() + } else if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_WII_SD_CARD_IMAGE_PATH) || + !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_WII_SD_CARD_SYNC_FOLDER_PATH) + ) { + MaterialAlertDialogBuilder(activity) + .setMessage(R.string.unavailable_paths) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + SettingsActivity.launch(activity, MenuTag.CONFIG_WII) + } + .setNeutralButton(R.string.continue_anyway) { _: DialogInterface?, _: Int -> + continueCallback.run() + } + .show() + } else { + continueCallback.run() + } + } + } + + @JvmStatic + fun launchSystemMenu(activity: FragmentActivity) { + if (ignoreLaunchRequests) + return + + performLaunchChecks(activity) { launchSystemMenuWithoutChecks(activity) } + } + + private fun launchSystemMenuWithoutChecks(activity: FragmentActivity) { + ignoreLaunchRequests = true + val launcher = Intent(activity, EmulationActivity::class.java) + launcher.putExtra(EXTRA_SYSTEM_MENU, true) + activity.startActivity(launcher) + } + + @JvmStatic + fun stopIgnoringLaunchRequests() { + ignoreLaunchRequests = false + } + + private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean { + if (view == null) + return true + + val viewBounds = Rect() + view.getGlobalVisibleRect(viewBounds) + return !viewBounds.contains(x.roundToInt(), y.roundToInt()) + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/riivolution/ui/RiivolutionBootActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/riivolution/ui/RiivolutionBootActivity.kt index f98af62592..d38f282f64 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/riivolution/ui/RiivolutionBootActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/riivolution/ui/RiivolutionBootActivity.kt @@ -50,7 +50,7 @@ class RiivolutionBootActivity : AppCompatActivity() { binding.textSdRoot.text = getString(R.string.riivolution_sd_root, "$loadPath/Riivolution") binding.buttonStart.setOnClickListener { if (patches != null) patches!!.saveConfig() - EmulationActivity.launch(this, path, true) + EmulationActivity.launch(this, path!!, true) } lifecycleScope.launch { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index 2a808f8dc5..5a0d02be42 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -319,7 +319,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C } else { - if (!EmulationActivity.getHasUserPausedEmulation() && !NativeLibrary.IsShowingAlertMessage()) + if (!EmulationActivity.Companion.getHasUserPausedEmulation() && + !NativeLibrary.IsShowingAlertMessage()) { Log.debug("[EmulationFragment] Resuming emulation."); NativeLibrary.UnPauseEmulation(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java index 25263e1ffd..5f8ce06013 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java @@ -196,7 +196,7 @@ public final class MenuFragment extends Fragment implements View.OnClickListener private void updatePauseUnpauseVisibility() { - boolean paused = EmulationActivity.getHasUserPausedEmulation(); + boolean paused = EmulationActivity.Companion.getHasUserPausedEmulation(); mBinding.menuUnpauseEmulation.setVisibility(paused ? View.VISIBLE : View.GONE); mBinding.menuPauseEmulation.setVisibility(paused ? View.GONE : View.VISIBLE); From cbca383bd2f91239a37691b61db0e66f391263ad Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 3 Jun 2023 22:23:47 -0400 Subject: [PATCH 2/3] Android: Convert CustomFilePickerActivity to Kotlin --- .../activities/CustomFilePickerActivity.java | 51 ------------------- .../activities/CustomFilePickerActivity.kt | 47 +++++++++++++++++ 2 files changed, 47 insertions(+), 51 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.java deleted file mode 100644 index 29a968d50c..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.java +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.activities; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; - -import androidx.annotation.Nullable; - -import com.nononsenseapps.filepicker.AbstractFilePickerFragment; -import com.nononsenseapps.filepicker.FilePickerActivity; - -import org.dolphinemu.dolphinemu.fragments.CustomFilePickerFragment; - -import java.io.File; -import java.util.HashSet; - -public class CustomFilePickerActivity extends FilePickerActivity -{ - public static final String EXTRA_EXTENSIONS = "dolphinemu.org.filepicker.extensions"; - - private HashSet mExtensions; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - if (intent != null) - { - mExtensions = (HashSet) intent.getSerializableExtra(EXTRA_EXTENSIONS); - } - } - - @Override - protected AbstractFilePickerFragment getFragment( - @Nullable final String startPath, final int mode, final boolean allowMultiple, - final boolean allowCreateDir, final boolean allowExistingFile, - final boolean singleClick) - { - CustomFilePickerFragment fragment = new CustomFilePickerFragment(); - // startPath is allowed to be null. In that case, default folder should be SD-card and not "/" - fragment.setArgs( - startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(), - mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick); - fragment.setExtensions(mExtensions); - return fragment; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.kt new file mode 100644 index 0000000000..3b48c62f12 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/CustomFilePickerActivity.kt @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.activities + +import android.os.Bundle +import android.os.Environment +import com.nononsenseapps.filepicker.AbstractFilePickerFragment +import com.nononsenseapps.filepicker.FilePickerActivity +import org.dolphinemu.dolphinemu.fragments.CustomFilePickerFragment +import java.io.File + +class CustomFilePickerActivity : FilePickerActivity() { + private var extensions: HashSet? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent != null) { + extensions = intent.getSerializableExtra(EXTRA_EXTENSIONS) as HashSet? + } + } + + override fun getFragment( + startPath: String?, + mode: Int, + allowMultiple: Boolean, + allowCreateDir: Boolean, + allowExistingFile: Boolean, + singleClick: Boolean + ): AbstractFilePickerFragment { + val fragment = CustomFilePickerFragment() + // startPath is allowed to be null. In that case, default folder should be SD-card and not "/" + fragment.setArgs( + startPath ?: Environment.getExternalStorageDirectory().path, + mode, + allowMultiple, + allowCreateDir, + allowExistingFile, + singleClick + ) + fragment.setExtensions(extensions) + return fragment + } + + companion object { + const val EXTRA_EXTENSIONS = "dolphinemu.org.filepicker.extensions" + } +} From e6d8694cbecec742fab5bdabf0fad9a37a4629e7 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 3 Jun 2023 22:24:02 -0400 Subject: [PATCH 3/3] Android: Convert AppLinkActivity to Kotlin --- .../activities/AppLinkActivity.java | 128 ------------------ .../dolphinemu/activities/AppLinkActivity.kt | 104 ++++++++++++++ 2 files changed, 104 insertions(+), 128 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java deleted file mode 100644 index 924b5911c0..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.activities; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; - -import androidx.fragment.app.FragmentActivity; - -import org.dolphinemu.dolphinemu.model.GameFile; -import org.dolphinemu.dolphinemu.services.GameFileCacheManager; -import org.dolphinemu.dolphinemu.ui.main.TvMainActivity; -import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; -import org.dolphinemu.dolphinemu.utils.AppLinkHelper; -import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; - -/** - * Linker between leanback homescreen and app - */ -public class AppLinkActivity extends FragmentActivity -{ - private static final String TAG = "AppLinkActivity"; - - private AppLinkHelper.PlayAction playAction; - private AfterDirectoryInitializationRunner mAfterDirectoryInitializationRunner; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - Intent intent = getIntent(); - Uri uri = intent.getData(); - - Log.v(TAG, uri.toString()); - - if (uri.getPathSegments().isEmpty()) - { - Log.e(TAG, "Invalid uri " + uri); - finish(); - return; - } - - AppLinkHelper.AppLinkAction action = AppLinkHelper.extractAction(uri); - switch (action.getAction()) - { - case AppLinkHelper.PLAY: - playAction = (AppLinkHelper.PlayAction) action; - initResources(); - break; - case AppLinkHelper.BROWSE: - browse(); - break; - default: - throw new IllegalArgumentException("Invalid Action " + action); - } - } - - /** - * Need to init these since they usually occur in the main activity. - */ - private void initResources() - { - mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); - mAfterDirectoryInitializationRunner.runWithLifecycle(this, () -> tryPlay(playAction)); - - GameFileCacheManager.isLoading().observe(this, (isLoading) -> - { - if (!isLoading && DirectoryInitialization.areDolphinDirectoriesReady()) - { - tryPlay(playAction); - } - }); - - DirectoryInitialization.start(this); - GameFileCacheManager.startLoad(); - } - - /** - * Action if channel icon is selected - */ - private void browse() - { - Intent openApp = new Intent(this, TvMainActivity.class); - startActivity(openApp); - - finish(); - } - - private void tryPlay(AppLinkHelper.PlayAction action) - { - // TODO: This approach of getting the game from the game file cache without rescanning the - // library means that we can fail to launch games if the cache file has been deleted. - - GameFile game = GameFileCacheManager.getGameFileByGameId(action.getGameId()); - - // If game == null and the load isn't done, wait for the next GameFileCacheService broadcast. - // If game == null and the load is done, call play with a null game, making us exit in failure. - if (game != null || !GameFileCacheManager.isLoading().getValue()) - { - play(action, game); - } - } - - /** - * Action if program(game) is selected - */ - private void play(AppLinkHelper.PlayAction action, GameFile game) - { - Log.d(TAG, "Playing game " - + action.getGameId() - + " from channel " - + action.getChannelId()); - - if (game == null) - Log.e(TAG, "Invalid Game: " + action.getGameId()); - else - startGame(game); - finish(); - } - - private void startGame(GameFile game) - { - mAfterDirectoryInitializationRunner.cancel(); - EmulationActivity.launch(this, GameFileCacheManager.findSecondDiscAndGetPaths(game), false); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.kt new file mode 100644 index 0000000000..f3f318202e --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.kt @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.activities + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.FragmentActivity +import org.dolphinemu.dolphinemu.model.GameFile +import org.dolphinemu.dolphinemu.services.GameFileCacheManager +import org.dolphinemu.dolphinemu.ui.main.TvMainActivity +import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner +import org.dolphinemu.dolphinemu.utils.AppLinkHelper +import org.dolphinemu.dolphinemu.utils.AppLinkHelper.PlayAction +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization + +/** + * Linker between leanback homescreen and app + */ +class AppLinkActivity : FragmentActivity() { + private lateinit var playAction: PlayAction + private lateinit var afterDirectoryInitializationRunner: AfterDirectoryInitializationRunner + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val uri = intent.data!! + + Log.v(TAG, uri.toString()) + + if (uri.pathSegments.isEmpty()) { + Log.e(TAG, "Invalid uri $uri") + finish() + return + } + + val action = AppLinkHelper.extractAction(uri) + when (action.action) { + AppLinkHelper.PLAY -> { + playAction = action as PlayAction + initResources() + } + + AppLinkHelper.BROWSE -> browse() + else -> throw IllegalArgumentException("Invalid Action $action") + } + } + + /** + * Need to init these since they usually occur in the main activity. + */ + private fun initResources() { + afterDirectoryInitializationRunner = AfterDirectoryInitializationRunner() + afterDirectoryInitializationRunner.runWithLifecycle(this) { tryPlay(playAction) } + + GameFileCacheManager.isLoading().observe(this) { isLoading: Boolean? -> + if (!isLoading!! && DirectoryInitialization.areDolphinDirectoriesReady()) { + tryPlay(playAction) + } + } + + DirectoryInitialization.start(this) + GameFileCacheManager.startLoad() + } + + /** + * Action if channel icon is selected + */ + private fun browse() { + val openApp = Intent(this, TvMainActivity::class.java) + startActivity(openApp) + + finish() + } + + private fun tryPlay(action: PlayAction) { + // TODO: This approach of getting the game from the game file cache without rescanning the + // library means that we can fail to launch games if the cache file has been deleted. + val game = GameFileCacheManager.getGameFileByGameId(action.gameId) + + // If game == null and the load isn't done, wait for the next GameFileCacheService broadcast. + // If game == null and the load is done, call play with a null game, making us exit in failure. + if (game != null || !GameFileCacheManager.isLoading().value!!) { + play(action, game) + } + } + + /** + * Action if program(game) is selected + */ + private fun play(action: PlayAction, game: GameFile?) { + Log.d(TAG, "Playing game ${action.gameId} from channel ${action.channelId}") + game?.let { startGame(it) } ?: Log.e(TAG, "Invalid Game: " + action.gameId) + finish() + } + + private fun startGame(game: GameFile) { + afterDirectoryInitializationRunner.cancel() + EmulationActivity.launch(this, GameFileCacheManager.findSecondDiscAndGetPaths(game), false) + } + + companion object { + private const val TAG = "AppLinkActivity" + } +}