summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java51
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java325
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java171
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java50
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java178
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java48
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java440
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java20
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java35
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java33
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java48
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java49
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java48
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java34
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java27
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java66
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java39
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java52
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java104
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java70
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java71
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java55
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java34
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java151
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java99
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java65
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt76
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java157
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java76
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java77
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java17
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java28
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java58
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java70
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java70
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java7
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java108
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java69
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java8
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java8
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java43
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java58
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java54
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java37
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java104
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java70
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java761
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java31
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java67
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java67
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java164
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java20
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java267
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java10
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java10
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java65
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java109
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java58
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java128
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java97
64 files changed, 3201 insertions, 2226 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
index d6fc236771..68fdfee277 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
@@ -18,7 +18,6 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.dialogs.AlertMessage;
import org.dolphinemu.dolphinemu.utils.CompressCallback;
import org.dolphinemu.dolphinemu.utils.Log;
-import org.dolphinemu.dolphinemu.utils.Rumble;
import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
@@ -44,7 +43,7 @@ public final class NativeLibrary
}
/**
- * Button type for use in onTouchEvent
+ * Button type, for legacy use only
*/
public static final class ButtonType
{
@@ -235,52 +234,6 @@ public final class NativeLibrary
}
/**
- * Default touchscreen device
- */
- public static final String TouchScreenDevice = "Touchscreen";
-
- /**
- * Handles button press events for a gamepad.
- *
- * @param Device The input descriptor of the gamepad.
- * @param Button Key code identifying which button was pressed.
- * @param Action Mask identifying which action is happening (button pressed down, or button released).
- * @return If we handled the button press.
- */
- public static native boolean onGamePadEvent(String Device, int Button, int Action);
-
- /**
- * Handles gamepad movement events.
- *
- * @param Device The device ID of the gamepad.
- * @param Axis The axis ID
- * @param Value The value of the axis represented by the given ID.
- */
- public static native void onGamePadMoveEvent(String Device, int Axis, float Value);
-
- /**
- * Rumble sent from native. Currently only supports phone rumble.
- *
- * @param padID Ignored for now. Future use would be to pass rumble to a connected controller
- * @param state Ignored for now since phone rumble can't just be 'turned' on/off
- */
- @Keep
- public static void rumble(int padID, double state)
- {
- final EmulationActivity emulationActivity = sEmulationActivity.get();
- if (emulationActivity == null)
- {
- Log.warning("[NativeLibrary] EmulationActivity is null");
- return;
- }
-
- Rumble.checkRumble(padID, state);
- }
-
- public static native void SetMotionSensorsEnabled(boolean accelerometerEnabled,
- boolean gyroscopeEnabled);
-
- /**
* Gets the Dolphin version string.
*
* @return the Dolphin version string.
@@ -451,8 +404,6 @@ public final class NativeLibrary
*/
public static native void RefreshWiimotes();
- public static native void ReloadWiimoteConfig();
-
public static native LinkedHashMap<String, String> GetLogTypeNames();
public static native void ReloadLoggerConfig();
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
index 808adbff26..9aebeb1ded 100644
--- 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
@@ -2,7 +2,6 @@
package org.dolphinemu.dolphinemu.activities;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -11,7 +10,6 @@ import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.util.SparseIntArray;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -40,15 +38,15 @@ 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.DialogIrSensitivityBinding;
import org.dolphinemu.dolphinemu.databinding.DialogSkylandersManagerBinding;
+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.settings.utils.SettingsFile;
import org.dolphinemu.dolphinemu.features.skylanders.SkylanderConfig;
import org.dolphinemu.dolphinemu.features.skylanders.model.Skylander;
import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlot;
@@ -61,14 +59,9 @@ 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.ControllerMappingHelper;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
-import org.dolphinemu.dolphinemu.utils.IniFile;
-import org.dolphinemu.dolphinemu.utils.MotionListener;
-import org.dolphinemu.dolphinemu.utils.Rumble;
import org.dolphinemu.dolphinemu.utils.ThemeHelper;
-import java.io.File;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
@@ -89,7 +82,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
private EmulationFragment mEmulationFragment;
private SharedPreferences mPreferences;
- private MotionListener mMotionListener;
private Settings mSettings;
@@ -127,9 +119,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
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_SET_IR_SENSITIVITY, MENU_ACTION_CHOOSE_DOUBLETAP, MENU_ACTION_MOTION_CONTROLS,
- MENU_ACTION_PAUSE_EMULATION, MENU_ACTION_UNPAUSE_EMULATION, MENU_ACTION_OVERLAY_CONTROLS,
- MENU_ACTION_SETTINGS, MENU_ACTION_SKYLANDERS})
+ MENU_ACTION_CHOOSE_DOUBLETAP, MENU_ACTION_PAUSE_EMULATION,
+ MENU_ACTION_UNPAUSE_EMULATION, MENU_ACTION_OVERLAY_CONTROLS, MENU_ACTION_SETTINGS,
+ MENU_ACTION_SKYLANDERS})
public @interface MenuAction
{
}
@@ -159,13 +151,10 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
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_RUMBLE = 25;
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_SET_IR_SENSITIVITY = 29;
public static final int MENU_ACTION_CHOOSE_DOUBLETAP = 30;
- public static final int MENU_ACTION_MOTION_CONTROLS = 31;
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;
@@ -194,19 +183,14 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
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_rumble, EmulationActivity.MENU_ACTION_RUMBLE);
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_set_ir_sensitivity,
- EmulationActivity.MENU_SET_IR_SENSITIVITY);
buttonsActionsMap.append(R.id.menu_emulation_choose_doubletap,
EmulationActivity.MENU_ACTION_CHOOSE_DOUBLETAP);
- buttonsActionsMap.append(R.id.menu_emulation_motion_controls,
- EmulationActivity.MENU_ACTION_MOTION_CONTROLS);
}
public static void launch(FragmentActivity activity, String filePath, boolean riivolution)
@@ -302,30 +286,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
sIgnoreLaunchRequests = false;
}
- public static void updateWiimoteNewIniPreferences(Context context)
- {
- updateWiimoteNewController(InputOverlay.getConfiguredControllerType(context), context);
- updateWiimoteNewImuIr(IntSetting.MAIN_MOTION_CONTROLS.getIntGlobal());
- }
-
- private static void updateWiimoteNewController(int value, Context context)
- {
- File wiimoteNewFile = SettingsFile.getSettingsFile(Settings.FILE_WIIMOTE);
- IniFile wiimoteNewIni = new IniFile(wiimoteNewFile);
- wiimoteNewIni.setString("Wiimote1", "Extension",
- context.getResources().getStringArray(R.array.controllersValues)[value]);
- wiimoteNewIni.setBoolean("Wiimote1", "Options/Sideways Wiimote", value == 2);
- wiimoteNewIni.save(wiimoteNewFile);
- }
-
- private static void updateWiimoteNewImuIr(int value)
- {
- File wiimoteNewFile = SettingsFile.getSettingsFile(Settings.FILE_WIIMOTE);
- IniFile wiimoteNewIni = new IniFile(wiimoteNewFile);
- wiimoteNewIni.setBoolean("Wiimote1", "IMUIR/Enabled", value != 1);
- wiimoteNewIni.save(wiimoteNewFile);
- }
-
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -359,13 +319,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
updateOrientation();
- mMotionListener = new MotionListener(this);
-
// Set these options now so that the SurfaceView the game renders into is the right size.
enableFullscreenImmersive();
- Rumble.initRumble(this);
-
ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@@ -453,15 +409,14 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
updateOrientation();
- if (NativeLibrary.IsGameMetadataValid())
- updateMotionListener();
+ DolphinSensorEventListener.setDeviceRotation(
+ getWindowManager().getDefaultDisplay().getRotation());
}
@Override
protected void onPause()
{
super.onPause();
- mMotionListener.disable();
}
@Override
@@ -481,17 +436,8 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
}
setTitle(NativeLibrary.GetCurrentTitleDescription());
- updateMotionListener();
- mEmulationFragment.refreshInputOverlay();
- }
-
- private void updateMotionListener()
- {
- if (NativeLibrary.IsEmulatingWii() && IntSetting.MAIN_MOTION_CONTROLS.getInt(mSettings) != 2)
- mMotionListener.enable();
- else
- mMotionListener.disable();
+ mEmulationFragment.refreshInputOverlay(mSettings);
}
@Override
@@ -644,8 +590,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
// 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(mSettings));
- menu.findItem(R.id.menu_emulation_rumble)
- .setChecked(BooleanSetting.MAIN_PHONE_RUMBLE.getBoolean(mSettings));
if (wii)
{
menu.findItem(R.id.menu_emulation_ir_recenter)
@@ -685,10 +629,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
item.setChecked(!item.isChecked());
toggleJoystickRelCenter(item.isChecked());
break;
- case MENU_ACTION_RUMBLE:
- item.setChecked(!item.isChecked());
- toggleRumble(item.isChecked());
- break;
case MENU_SET_IR_RECENTER:
item.setChecked(!item.isChecked());
toggleRecenter(item.isChecked());
@@ -721,7 +661,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
adjustScale();
break;
- // (Wii games only) Change the controller for the input overlay.
+ // Change the controller for the input overlay.
case MENU_ACTION_CHOOSE_CONTROLLER:
chooseController();
break;
@@ -823,18 +763,10 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
setIRMode();
break;
- case MENU_SET_IR_SENSITIVITY:
- setIRSensitivity();
- break;
-
case MENU_ACTION_CHOOSE_DOUBLETAP:
chooseDoubleTapButton();
break;
- case MENU_ACTION_MOTION_CONTROLS:
- showMotionControlsOptions();
- break;
-
case MENU_ACTION_SETTINGS:
SettingsActivity.launch(this, MenuTag.SETTINGS);
break;
@@ -859,12 +791,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
BooleanSetting.MAIN_JOYSTICK_REL_CENTER.setBoolean(mSettings, state);
}
- private void toggleRumble(boolean state)
- {
- BooleanSetting.MAIN_PHONE_RUMBLE.setBoolean(mSettings, state);
- Rumble.setPhoneVibrator(state, this);
- }
-
private void toggleRecenter(boolean state)
{
BooleanSetting.MAIN_IR_ALWAYS_RECENTER.setBoolean(mSettings, state);
@@ -889,26 +815,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
- if (mMenuVisible || event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+ if (!mMenuVisible)
{
- return super.dispatchKeyEvent(event);
+ if (ControllerInterface.dispatchKeyEvent(event))
+ {
+ return true;
+ }
}
- int action;
-
- switch (event.getAction())
- {
- case KeyEvent.ACTION_DOWN:
- action = NativeLibrary.ButtonState.PRESSED;
- break;
- case KeyEvent.ACTION_UP:
- action = NativeLibrary.ButtonState.RELEASED;
- break;
- default:
- return false;
- }
- InputDevice input = event.getDevice();
- return NativeLibrary.onGamePadEvent(input.getDescriptor(), event.getKeyCode(), action);
+ return super.dispatchKeyEvent(event);
}
private void toggleControls()
@@ -916,9 +831,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.emulation_toggle_controls);
- int currentController = InputOverlay.getConfiguredControllerType(this);
+ int currentController = InputOverlay.getConfiguredControllerType(mSettings);
- if (!NativeLibrary.IsEmulatingWii() || currentController == InputOverlay.OVERLAY_GAMECUBE)
+ if (currentController == InputOverlay.OVERLAY_GAMECUBE)
{
boolean[] gcEnabledButtons = new boolean[11];
String gcSettingBase = "MAIN_BUTTON_TOGGLE_GC_";
@@ -972,7 +887,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
builder.setNeutralButton(R.string.emulation_toggle_all,
(dialogInterface, i) -> mEmulationFragment.toggleInputOverlayVisibility(mSettings))
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
- mEmulationFragment.refreshInputOverlay())
+ mEmulationFragment.refreshInputOverlay(mSettings))
.show();
}
@@ -980,7 +895,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
{
int currentValue = IntSetting.MAIN_DOUBLE_TAP_BUTTON.getInt(mSettings);
- int buttonList = InputOverlay.getConfiguredControllerType(this) ==
+ int buttonList = InputOverlay.getConfiguredControllerType(mSettings) ==
InputOverlay.OVERLAY_WIIMOTE_CLASSIC ? R.array.doubleTapWithClassic : R.array.doubleTap;
int checkedItem = -1;
@@ -999,7 +914,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
(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())
+ (dialogInterface, i) -> mEmulationFragment.initInputPointer(mSettings))
.show();
}
@@ -1033,55 +948,75 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
{
IntSetting.MAIN_CONTROL_SCALE.setInt(mSettings, (int) scaleSlider.getValue());
IntSetting.MAIN_CONTROL_OPACITY.setInt(mSettings, (int) sliderOpacity.getValue());
- mEmulationFragment.refreshInputOverlay();
+ mEmulationFragment.refreshInputOverlay(mSettings);
})
.setNeutralButton(R.string.default_values, (dialog, which) ->
{
IntSetting.MAIN_CONTROL_SCALE.delete(mSettings);
IntSetting.MAIN_CONTROL_OPACITY.delete(mSettings);
- mEmulationFragment.refreshInputOverlay();
+ mEmulationFragment.refreshInputOverlay(mSettings);
})
.show();
}
+ private void addControllerIfNotNone(List<CharSequence> entries, List<Integer> values,
+ IntSetting controller, int entry, int value)
+ {
+ if (controller.getInt(mSettings) != 0)
+ {
+ entries.add(getString(entry));
+ values.add(value);
+ }
+ }
+
private void chooseController()
{
+ ArrayList<CharSequence> entries = new ArrayList<>();
+ ArrayList<Integer> 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(mSettings);
+
+ 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(R.array.controllersEntries,
- InputOverlay.getConfiguredControllerType(this),
+ .setSingleChoiceItems(entries.toArray(new CharSequence[]{}), checkedItem,
(dialog, indexSelected) ->
- {
- editor.putInt("wiiController", indexSelected);
-
- updateWiimoteNewController(indexSelected, this);
- NativeLibrary.ReloadWiimoteConfig();
- })
+ controllerSetting.setInt(mSettings, values.get(indexSelected)))
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
{
editor.apply();
- mEmulationFragment.refreshInputOverlay();
+ mEmulationFragment.refreshInputOverlay(mSettings);
})
- .show();
- }
-
- private void showMotionControlsOptions()
- {
- new MaterialAlertDialogBuilder(this)
- .setTitle(R.string.emulation_motion_controls)
- .setSingleChoiceItems(R.array.motionControlsEntries,
- IntSetting.MAIN_MOTION_CONTROLS.getInt(mSettings),
- (dialog, indexSelected) ->
- {
- IntSetting.MAIN_MOTION_CONTROLS.setInt(mSettings, indexSelected);
-
- updateMotionListener();
-
- updateWiimoteNewImuIr(indexSelected);
- NativeLibrary.ReloadWiimoteConfig();
- })
- .setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss())
+ .setNeutralButton(R.string.emulation_more_controller_settings,
+ (dialogInterface, i) -> SettingsActivity.launch(this, MenuTag.SETTINGS))
.show();
}
@@ -1098,88 +1033,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
.show();
}
- private void setIRSensitivity()
- {
- // IR settings always get saved per-game since WiimoteNew.ini is wiped upon reinstall.
- File file = SettingsFile.getCustomGameSettingsFile(NativeLibrary.GetCurrentGameID());
- IniFile ini = new IniFile(file);
-
- int ir_pitch = ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH, 20);
-
- DialogIrSensitivityBinding dialogBinding =
- DialogIrSensitivityBinding.inflate(getLayoutInflater());
-
- TextView text_slider_value_pitch = dialogBinding.textIrPitch;
- TextView units = dialogBinding.textIrPitchUnits;
- Slider slider_pitch = dialogBinding.sliderPitch;
-
- text_slider_value_pitch.setText(String.valueOf(ir_pitch));
- units.setText(getString(R.string.pitch));
- slider_pitch.setValueTo(100);
- slider_pitch.setValue(ir_pitch);
- slider_pitch.setStepSize(1);
- slider_pitch.addOnChangeListener(
- (slider, progress, fromUser) -> text_slider_value_pitch.setText(
- String.valueOf((int) progress)));
-
- int ir_yaw = ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW, 25);
-
- TextView text_slider_value_yaw = dialogBinding.textIrYaw;
- TextView units_yaw = dialogBinding.textIrYawUnits;
- Slider seekbar_yaw = dialogBinding.sliderYaw;
-
- text_slider_value_yaw.setText(String.valueOf(ir_yaw));
- units_yaw.setText(getString(R.string.yaw));
- seekbar_yaw.setValueTo(100);
- seekbar_yaw.setValue(ir_yaw);
- seekbar_yaw.setStepSize(1);
- seekbar_yaw.addOnChangeListener((slider, progress, fromUser) -> text_slider_value_yaw.setText(
- String.valueOf((int) progress)));
-
- int ir_vertical_offset =
- ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET, 10);
-
- TextView text_slider_value_vertical_offset = dialogBinding.textIrVerticalOffset;
- TextView units_vertical_offset = dialogBinding.textIrVerticalOffsetUnits;
- Slider seekbar_vertical_offset = dialogBinding.sliderVerticalOffset;
-
- text_slider_value_vertical_offset.setText(String.valueOf(ir_vertical_offset));
- units_vertical_offset.setText(getString(R.string.vertical_offset));
- seekbar_vertical_offset.setValueTo(100);
- seekbar_vertical_offset.setValue(ir_vertical_offset);
- seekbar_vertical_offset.setStepSize(1);
- seekbar_vertical_offset.addOnChangeListener(
- (slider, progress, fromUser) -> text_slider_value_vertical_offset.setText(
- String.valueOf((int) progress)));
-
- new MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.emulation_ir_sensitivity))
- .setView(dialogBinding.getRoot())
- .setPositiveButton(R.string.ok, (dialogInterface, i) ->
- {
- ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH,
- text_slider_value_pitch.getText().toString());
- ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW,
- text_slider_value_yaw.getText().toString());
- ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET,
- text_slider_value_vertical_offset.getText().toString());
- ini.save(file);
-
- NativeLibrary.ReloadWiimoteConfig();
- })
- .setNegativeButton(R.string.cancel, null)
- .setNeutralButton(R.string.default_values, (dialogInterface, i) ->
- {
- ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH);
- ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW);
- ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET);
- ini.save(file);
-
- NativeLibrary.ReloadWiimoteConfig();
- })
- .show();
- }
-
private void showSkylanderPortalSettings()
{
mSkylandersBinding =
@@ -1212,7 +1065,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.emulation_touch_overlay_reset))
.setPositiveButton(R.string.yes,
- (dialogInterface, i) -> mEmulationFragment.resetInputOverlay())
+ (dialogInterface, i) -> mEmulationFragment.resetInputOverlay(mSettings))
.setNegativeButton(R.string.cancel, null)
.show();
}
@@ -1266,41 +1119,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event)
{
- if (mMenuVisible)
- {
- return false;
- }
-
- if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0))
+ if (!mMenuVisible)
{
- return super.dispatchGenericMotionEvent(event);
- }
-
- // Don't attempt to do anything if we are disconnecting a device.
- if (event.getActionMasked() == MotionEvent.ACTION_CANCEL)
- return true;
-
- InputDevice input = event.getDevice();
- List<InputDevice.MotionRange> motions = input.getMotionRanges();
-
- for (InputDevice.MotionRange range : motions)
- {
- int axis = range.getAxis();
- float origValue = event.getAxisValue(axis);
- float value = ControllerMappingHelper.scaleAxis(input, axis, origValue);
- // If the input is still in the "flat" area, that means it's really zero.
- // This is used to compensate for imprecision in joysticks.
- if (Math.abs(value) > range.getFlat())
+ if (ControllerInterface.dispatchGenericMotionEvent(event))
{
- NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, value);
- }
- else
- {
- NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, 0.0f);
+ return true;
}
}
- return true;
+ return super.dispatchGenericMotionEvent(event);
}
private void showSubMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad)
@@ -1333,7 +1160,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
public void initInputPointer()
{
- mEmulationFragment.initInputPointer();
+ mEmulationFragment.initInputPointer(mSettings);
}
@Override
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java
deleted file mode 100644
index b0316f94cc..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java
+++ /dev/null
@@ -1,171 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.dialogs;
-
-import android.content.Context;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-
-import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
-import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
-import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
-import org.dolphinemu.dolphinemu.utils.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link AlertDialog} derivative that listens for
- * motion events from controllers and joysticks.
- */
-public final class MotionAlertDialog extends AlertDialog
-{
- // The selected input preference
- private final InputBindingSetting setting;
- private final ArrayList<Float> mPreviousValues = new ArrayList<>();
- private int mPrevDeviceId = 0;
- private boolean mWaitingForEvent = true;
- private SettingsAdapter mAdapter;
-
- /**
- * Constructor
- *
- * @param context The current {@link Context}.
- * @param setting The Preference to show this dialog for.
- */
- public MotionAlertDialog(Context context, InputBindingSetting setting, SettingsAdapter adapter)
- {
- super(context);
-
- this.setting = setting;
- mAdapter = adapter;
- }
-
- public boolean onKeyEvent(int keyCode, KeyEvent event)
- {
- Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
- if (event.getAction() == KeyEvent.ACTION_UP)
- {
- if (!ControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode))
- {
- setting.onKeyInput(mAdapter.getSettings(), event);
- dismiss();
- }
- // Even if we ignore the key, we still consume it. Thus return true regardless.
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event)
- {
- // Intended for devices with no touchscreen or mouse
- if (keyCode == KeyEvent.KEYCODE_BACK)
- {
- setting.clearValue(mAdapter.getSettings());
- dismiss();
- return true;
- }
- return super.onKeyLongPress(keyCode, event);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event)
- {
- // Handle this key if we care about it, otherwise pass it down the framework
- return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event)
- {
- // Handle this event if we care about it, otherwise pass it down the framework
- return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
- }
-
- private boolean onMotionEvent(MotionEvent event)
- {
- if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
- return false;
- if (event.getAction() != MotionEvent.ACTION_MOVE)
- return false;
-
- InputDevice input = event.getDevice();
-
- List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
-
- if (input.getId() != mPrevDeviceId)
- {
- mPreviousValues.clear();
- }
- mPrevDeviceId = input.getId();
- boolean firstEvent = mPreviousValues.isEmpty();
-
- int numMovedAxis = 0;
- float axisMoveValue = 0.0f;
- InputDevice.MotionRange lastMovedRange = null;
- char lastMovedDir = '?';
- if (mWaitingForEvent)
- {
- for (int i = 0; i < motionRanges.size(); i++)
- {
- InputDevice.MotionRange range = motionRanges.get(i);
- int axis = range.getAxis();
- float origValue = event.getAxisValue(axis);
- float value = ControllerMappingHelper.scaleAxis(input, axis, origValue);
- if (firstEvent)
- {
- mPreviousValues.add(value);
- }
- else
- {
- float previousValue = mPreviousValues.get(i);
-
- // Only handle the axes that are not neutral (more than 0.5)
- // but ignore any axis that has a constant value (e.g. always 1)
- if (Math.abs(value) > 0.5f && value != previousValue)
- {
- // It is common to have multiple axes with the same physical input. For example,
- // shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
- // To handle this, we ignore an axis motion that's the exact same as a motion
- // we already saw. This way, we ignore axes with two names, but catch the case
- // where a joystick is moved in two directions.
- // ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
- if (value != axisMoveValue)
- {
- axisMoveValue = value;
- numMovedAxis++;
- lastMovedRange = range;
- lastMovedDir = value < 0.0f ? '-' : '+';
- }
- }
- // Special case for d-pads (axis value jumps between 0 and 1 without any values
- // in between). Without this, the user would need to press the d-pad twice
- // due to the first press being caught by the "if (firstEvent)" case further up.
- else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f)
- {
- numMovedAxis++;
- lastMovedRange = range;
- lastMovedDir = previousValue < 0.0f ? '-' : '+';
- }
- }
-
- mPreviousValues.set(i, value);
- }
-
- // If only one axis moved, that's the winner.
- if (numMovedAxis == 1)
- {
- mWaitingForEvent = false;
- setting.onMotionInput(mAdapter.getSettings(), input, lastMovedRange, lastMovedDir);
- dismiss();
- }
- }
- return true;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java
new file mode 100644
index 0000000000..95aa080429
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup;
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.Settings;
+
+public class ControlGroupEnabledSetting implements AbstractBooleanSetting
+{
+ private final ControlGroup mControlGroup;
+
+ public ControlGroupEnabledSetting(ControlGroup controlGroup)
+ {
+ mControlGroup = controlGroup;
+ }
+
+ @Override
+ public boolean getBoolean(Settings settings)
+ {
+ return mControlGroup.getEnabled();
+ }
+
+ @Override
+ public void setBoolean(Settings settings, boolean newValue)
+ {
+ mControlGroup.setEnabled(newValue);
+ }
+
+ @Override
+ public boolean isOverridden(Settings settings)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isRuntimeEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean delete(Settings settings)
+ {
+ boolean newValue = mControlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO;
+ mControlGroup.setEnabled(newValue);
+
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java
new file mode 100644
index 0000000000..732ed97a7c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.dolphinemu.dolphinemu.DolphinApplication;
+import org.dolphinemu.dolphinemu.utils.LooperThread;
+
+/**
+ * This class interfaces with the native ControllerInterface,
+ * which is where the emulator core gets inputs from.
+ */
+public final class ControllerInterface
+{
+ private static final class InputDeviceListener implements InputManager.InputDeviceListener
+ {
+ @Override
+ public void onInputDeviceAdded(int deviceId)
+ {
+ // Simple implementation for now. We could do something fancier if we wanted to.
+ refreshDevices();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId)
+ {
+ // Simple implementation for now. We could do something fancier if we wanted to.
+ refreshDevices();
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId)
+ {
+ // Simple implementation for now. We could do something fancier if we wanted to.
+ refreshDevices();
+ }
+ }
+
+ private static InputDeviceListener mInputDeviceListener;
+ private static LooperThread mLooperThread;
+
+ /**
+ * Activities which want to pass on inputs to native code
+ * should call this in their own dispatchKeyEvent method.
+ *
+ * @return true if the emulator core seems to be interested in this event.
+ * false if the event should be passed on to the default dispatchKeyEvent.
+ */
+ public static native boolean dispatchKeyEvent(KeyEvent event);
+
+ /**
+ * Activities which want to pass on inputs to native code
+ * should call this in their own dispatchGenericMotionEvent method.
+ *
+ * @return true if the emulator core seems to be interested in this event.
+ * false if the event should be passed on to the default dispatchGenericMotionEvent.
+ */
+ public static native boolean dispatchGenericMotionEvent(MotionEvent event);
+
+ /**
+ * {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent.
+ *
+ * @return true if the emulator core seems to be interested in this event.
+ * false if the sensor can be suspended to save battery.
+ */
+ public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName,
+ float value);
+
+ /**
+ * Called when a sensor is suspended or unsuspended.
+ *
+ * @param deviceQualifier A string used by native code for uniquely identifying devices.
+ * @param axisNames The name of all axes for the sensor.
+ * @param suspended Whether the sensor is now suspended.
+ */
+ public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames,
+ boolean suspended);
+
+ /**
+ * Rescans for input devices.
+ */
+ public static native void refreshDevices();
+
+ public static native String[] getAllDeviceStrings();
+
+ @Nullable
+ public static native CoreDevice getDevice(String deviceString);
+
+ @Keep
+ private static void registerInputDeviceListener()
+ {
+ if (mLooperThread == null)
+ {
+ mLooperThread = new LooperThread("Hotplug thread");
+ mLooperThread.start();
+ }
+
+ if (mInputDeviceListener == null)
+ {
+ InputManager im = (InputManager)
+ DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
+
+ mInputDeviceListener = new InputDeviceListener();
+ im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper()));
+ }
+ }
+
+ @Keep
+ private static void unregisterInputDeviceListener()
+ {
+ if (mInputDeviceListener != null)
+ {
+ InputManager im = (InputManager)
+ DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
+
+ im.unregisterInputDeviceListener(mInputDeviceListener);
+ mInputDeviceListener = null;
+ }
+ }
+
+ @Keep @NonNull
+ private static DolphinVibratorManager getVibratorManager(InputDevice device)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ {
+ return new DolphinVibratorManagerPassthrough(device.getVibratorManager());
+ }
+ else
+ {
+ return new DolphinVibratorManagerCompat(device.getVibrator());
+ }
+ }
+
+ @Keep @NonNull
+ private static DolphinVibratorManager getSystemVibratorManager()
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ {
+ VibratorManager vibratorManager = (VibratorManager)
+ DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
+
+ if (vibratorManager != null)
+ return new DolphinVibratorManagerPassthrough(vibratorManager);
+ }
+
+ Vibrator vibrator = (Vibrator)
+ DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE);
+
+ return new DolphinVibratorManagerCompat(vibrator);
+ }
+
+ @Keep
+ private static void vibrate(@NonNull Vibrator vibrator)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ {
+ vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+ }
+ else
+ {
+ vibrator.vibrate(100);
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java
new file mode 100644
index 0000000000..b69e9a0236
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import androidx.annotation.Keep;
+
+/**
+ * Represents a C++ ciface::Core::Device.
+ */
+public final class CoreDevice
+{
+ /**
+ * Represents a C++ ciface::Core::Device::Control.
+ *
+ * This class is non-static to ensure that the CoreDevice parent does not get garbage collected
+ * while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.)
+ */
+ @SuppressWarnings("InnerClassMayBeStatic")
+ public final class Control
+ {
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private Control(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ public native String getName();
+ }
+
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private CoreDevice(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ @Override
+ protected native void finalize();
+
+ public native Control[] getInputs();
+
+ public native Control[] getOutputs();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java
new file mode 100644
index 0000000000..78d0e4785d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java
@@ -0,0 +1,440 @@
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.view.InputDevice;
+import android.view.Surface;
+
+import androidx.annotation.Keep;
+
+import org.dolphinemu.dolphinemu.DolphinApplication;
+import org.dolphinemu.dolphinemu.utils.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DolphinSensorEventListener implements SensorEventListener
+{
+ // Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
+ private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0;
+ // Set of three axes. Creates a negative companion to each axis.
+ private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1;
+
+ private static class AxisSetDetails
+ {
+ public final int firstAxisOfSet;
+ public final int axisSetType;
+
+ public AxisSetDetails(int firstAxisOfSet, int axisSetType)
+ {
+ this.firstAxisOfSet = firstAxisOfSet;
+ this.axisSetType = axisSetType;
+ }
+ }
+
+ private static class SensorDetails
+ {
+ public final int sensorType;
+ public final String[] axisNames;
+ public final AxisSetDetails[] axisSetDetails;
+ public boolean isSuspended = true;
+
+ public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
+ {
+ this.sensorType = sensorType;
+ this.axisNames = axisNames;
+ this.axisSetDetails = axisSetDetails;
+ }
+ }
+
+ private static int sDeviceRotation = Surface.ROTATION_0;
+
+ private final SensorManager mSensorManager;
+
+ private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
+
+ private final boolean mRotateCoordinatesForScreenOrientation;
+
+ private String mDeviceQualifier = "";
+
+ // The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
+ // permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly.
+ private static final int SAMPLING_PERIOD_US = 1000000 / 200;
+
+ @Keep
+ public DolphinSensorEventListener()
+ {
+ mSensorManager = (SensorManager)
+ DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE);
+ mRotateCoordinatesForScreenOrientation = true;
+
+ addSensors();
+ }
+
+ @Keep
+ public DolphinSensorEventListener(InputDevice inputDevice)
+ {
+ mRotateCoordinatesForScreenOrientation = false;
+
+ if (Build.VERSION.SDK_INT >= 31)
+ {
+ mSensorManager = inputDevice.getSensorManager();
+
+ // TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
+ // a sensor that we never registered as a listener for. The way our code is currently written,
+ // this causes a NullPointerException, but if we checked for null we would instead have the
+ // problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
+ // enabled. For now, let's comment out the ability to use InputDevice sensors.
+
+ //addSensors();
+ }
+ else
+ {
+ mSensorManager = null;
+ }
+ }
+
+ private void addSensors()
+ {
+ tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left",
+ "Accel Forward", "Accel Backward", "Accel Up", "Accel Down"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down",
+ "Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_LIGHT, "Light");
+
+ tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure");
+
+ tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature");
+
+ tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity");
+
+ tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left",
+ "Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION,
+ new String[]{"Linear Acceleration Right", "Linear Acceleration Left",
+ "Linear Acceleration Forward", "Linear Acceleration Backward",
+ "Linear Acceleration Up", "Linear Acceleration Down"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ // The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
+ // The directions of X and Y are flipped to match the Wii Remote coordinate system.
+ tryAddSensor(Sensor.TYPE_ROTATION_VECTOR,
+ new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-",
+ "Rotation Vector Y+", "Rotation Vector Z+",
+ "Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity");
+
+ tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature");
+
+ // The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
+ // The directions of X and Y are flipped to match the Wii Remote coordinate system.
+ tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR,
+ new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+",
+ "Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+",
+ "Game Rotation Vector Z-", "Game Rotation Vector R"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
+ new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down",
+ "Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left",
+ "Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right",
+ "Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right",
+ "Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
+ new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate");
+
+ if (Build.VERSION.SDK_INT >= 24)
+ {
+ tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat");
+ }
+
+ if (Build.VERSION.SDK_INT >= 26)
+ {
+ tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
+ new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left",
+ "Accel Uncalibrated Forward", "Accel Uncalibrated Backward",
+ "Accel Uncalibrated Up", "Accel Uncalibrated Down",
+ "Accel Bias Right", "Accel Bias Left", "Accel Bias Forward",
+ "Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
+ new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
+ }
+
+ if (Build.VERSION.SDK_INT >= 30)
+ {
+ tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle");
+ }
+
+ if (Build.VERSION.SDK_INT >= 33)
+ {
+ // The values provided by this sensor can be interpreted as an Euler vector.
+ // The directions of X and Y are flipped to match the Wii Remote coordinate system.
+ tryAddSensor(Sensor.TYPE_HEAD_TRACKER,
+ new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+",
+ "Head Rotation Vector Y-", "Head Rotation Vector Y+",
+ "Head Rotation Vector Z+", "Head Rotation Vector Z-",
+ "Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left",
+ "Head Yaw Left", "Head Yaw Right"},
+ new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
+ new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)});
+
+ tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"},
+ new AxisSetDetails[]{});
+ }
+ }
+
+ private void tryAddSensor(int sensorType, String axisName)
+ {
+ tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{});
+ }
+
+ private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
+ {
+ Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+ if (sensor != null)
+ {
+ mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails));
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent sensorEvent)
+ {
+ final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor);
+
+ final float[] values = sensorEvent.values;
+ final String[] axisNames = sensorDetails.axisNames;
+ final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails;
+
+ int eventAxisIndex = 0;
+ int detailsAxisIndex = 0;
+ int detailsAxisSetIndex = 0;
+ boolean keepSensorAlive = false;
+ while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
+ {
+ if (detailsAxisSetIndex < axisSetDetails.length &&
+ axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
+ {
+ int rotation = Surface.ROTATION_0;
+ if (mRotateCoordinatesForScreenOrientation &&
+ axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
+ {
+ rotation = sDeviceRotation;
+ }
+
+ float x, y;
+ switch (rotation)
+ {
+ default:
+ case Surface.ROTATION_0:
+ x = values[eventAxisIndex];
+ y = values[eventAxisIndex + 1];
+ break;
+ case Surface.ROTATION_90:
+ x = -values[eventAxisIndex + 1];
+ y = values[eventAxisIndex];
+ break;
+ case Surface.ROTATION_180:
+ x = -values[eventAxisIndex];
+ y = -values[eventAxisIndex + 1];
+ break;
+ case Surface.ROTATION_270:
+ x = values[eventAxisIndex + 1];
+ y = -values[eventAxisIndex];
+ break;
+ }
+
+ float z = values[eventAxisIndex + 2];
+
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex], x);
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex + 1], x);
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex + 2], y);
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex + 3], y);
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex + 4], z);
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex + 5], z);
+
+ eventAxisIndex += 3;
+ detailsAxisIndex += 6;
+ detailsAxisSetIndex++;
+ }
+ else
+ {
+ keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
+ axisNames[detailsAxisIndex], values[eventAxisIndex]);
+
+ eventAxisIndex++;
+ detailsAxisIndex++;
+ }
+ }
+
+ if (!keepSensorAlive)
+ {
+ setSensorSuspended(sensorEvent.sensor, sensorDetails, true);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int i)
+ {
+ // We don't care about this
+ }
+
+ /**
+ * The device qualifier set here will be passed on to native code,
+ * for the purpose of letting native code identify which device this object belongs to.
+ */
+ @Keep
+ public void setDeviceQualifier(String deviceQualifier)
+ {
+ mDeviceQualifier = deviceQualifier;
+ }
+
+ /**
+ * If a sensor has been suspended to save battery, this unsuspends it.
+ * If the sensor isn't currently suspended, nothing happens.
+ *
+ * @param axisName The name of any of the sensor's axes.
+ */
+ @Keep
+ public void requestUnsuspendSensor(String axisName)
+ {
+ for (Map.Entry<Sensor, SensorDetails> entry : mSensorDetails.entrySet())
+ {
+ if (Arrays.asList(entry.getValue().axisNames).contains(axisName))
+ {
+ setSensorSuspended(entry.getKey(), entry.getValue(), false);
+ }
+ }
+ }
+
+ private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend)
+ {
+ boolean changeOccurred = false;
+
+ synchronized (sensorDetails)
+ {
+ if (sensorDetails.isSuspended != suspend)
+ {
+ ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames,
+ suspend);
+
+ if (suspend)
+ mSensorManager.unregisterListener(this, sensor);
+ else
+ mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
+
+ sensorDetails.isSuspended = suspend;
+
+ changeOccurred = true;
+ }
+ }
+
+ if (changeOccurred)
+ {
+ Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName());
+ }
+ }
+
+ @Keep
+ public String[] getAxisNames()
+ {
+ ArrayList<String> axisNames = new ArrayList<>();
+
+ for (SensorDetails sensorDetails : getSensorDetailsSorted())
+ {
+ Collections.addAll(axisNames, sensorDetails.axisNames);
+ }
+
+ return axisNames.toArray(new String[]{});
+ }
+
+ @Keep
+ public boolean[] getNegativeAxes()
+ {
+ ArrayList<Boolean> negativeAxes = new ArrayList<>();
+
+ for (SensorDetails sensorDetails : getSensorDetailsSorted())
+ {
+ int eventAxisIndex = 0;
+ int detailsAxisIndex = 0;
+ int detailsAxisSetIndex = 0;
+ while (detailsAxisIndex < sensorDetails.axisNames.length)
+ {
+ if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length &&
+ sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
+ {
+ negativeAxes.add(false);
+ negativeAxes.add(true);
+ negativeAxes.add(false);
+ negativeAxes.add(true);
+ negativeAxes.add(false);
+ negativeAxes.add(true);
+
+ eventAxisIndex += 3;
+ detailsAxisIndex += 6;
+ detailsAxisSetIndex++;
+ }
+ else
+ {
+ negativeAxes.add(false);
+
+ eventAxisIndex++;
+ detailsAxisIndex++;
+ }
+ }
+ }
+
+ boolean[] result = new boolean[negativeAxes.size()];
+ for (int i = 0; i < result.length; i++)
+ {
+ result[i] = negativeAxes.get(i);
+ }
+
+ return result;
+ }
+
+ private List<SensorDetails> getSensorDetailsSorted()
+ {
+ ArrayList<SensorDetails> sensorDetails = new ArrayList<>(mSensorDetails.values());
+ Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType));
+ return sensorDetails;
+ }
+
+ /**
+ * Should be called when an activity or other component that uses sensor events is resumed.
+ *
+ * Sensor events that contain device coordinates will have the coordinates rotated by the value
+ * passed to this function.
+ *
+ * @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
+ */
+ public static void setDeviceRotation(int deviceRotation)
+ {
+ sDeviceRotation = deviceRotation;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java
new file mode 100644
index 0000000000..abc04d969d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import android.os.Vibrator;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+
+/**
+ * A wrapper around {@link android.os.VibratorManager}, for backwards compatibility.
+ */
+public interface DolphinVibratorManager
+{
+ @Keep @NonNull
+ Vibrator getVibrator(int vibratorId);
+
+ @Keep @NonNull
+ int[] getVibratorIds();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java
new file mode 100644
index 0000000000..40c4fa95d1
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import android.os.Vibrator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public final class DolphinVibratorManagerCompat implements DolphinVibratorManager
+{
+ private final Vibrator mVibrator;
+ private final int[] mIds;
+
+ public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator)
+ {
+ mVibrator = vibrator;
+ mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{};
+ }
+
+ @Override @NonNull
+ public Vibrator getVibrator(int vibratorId)
+ {
+ if (vibratorId > mIds.length)
+ throw new IndexOutOfBoundsException();
+
+ return mVibrator;
+ }
+
+ @Override @NonNull
+ public int[] getVibratorIds()
+ {
+ return mIds;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java
new file mode 100644
index 0000000000..2ca747f54f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import android.os.Build;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(api = Build.VERSION_CODES.S)
+public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager
+{
+ private final VibratorManager mVibratorManager;
+
+ public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager)
+ {
+ mVibratorManager = vibratorManager;
+ }
+
+ @Override @NonNull
+ public Vibrator getVibrator(int vibratorId)
+ {
+ return mVibratorManager.getVibrator(vibratorId);
+ }
+
+ @Override @NonNull
+ public int[] getVibratorIds()
+ {
+ return mVibratorManager.getVibratorIds();
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java
new file mode 100644
index 0000000000..7241d6e125
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.Settings;
+
+public class InputMappingBooleanSetting implements AbstractBooleanSetting
+{
+ private final NumericSetting mNumericSetting;
+
+ public InputMappingBooleanSetting(NumericSetting numericSetting)
+ {
+ mNumericSetting = numericSetting;
+ }
+
+ @Override
+ public boolean getBoolean(Settings settings)
+ {
+ return mNumericSetting.getBooleanValue();
+ }
+
+ @Override
+ public void setBoolean(Settings settings, boolean newValue)
+ {
+ mNumericSetting.setBooleanValue(newValue);
+ }
+
+ @Override
+ public boolean isOverridden(Settings settings)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isRuntimeEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean delete(Settings settings)
+ {
+ mNumericSetting.setBooleanValue(mNumericSetting.getBooleanDefaultValue());
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java
new file mode 100644
index 0000000000..e84b61a045
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.Settings;
+
+// Yes, floats are not the same thing as doubles... They're close enough, though
+public class InputMappingDoubleSetting implements AbstractFloatSetting
+{
+ private final NumericSetting mNumericSetting;
+
+ public InputMappingDoubleSetting(NumericSetting numericSetting)
+ {
+ mNumericSetting = numericSetting;
+ }
+
+ @Override
+ public float getFloat(Settings settings)
+ {
+ return (float) mNumericSetting.getDoubleValue();
+ }
+
+ @Override
+ public void setFloat(Settings settings, float newValue)
+ {
+ mNumericSetting.setDoubleValue(newValue);
+ }
+
+ @Override
+ public boolean isOverridden(Settings settings)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isRuntimeEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean delete(Settings settings)
+ {
+ mNumericSetting.setDoubleValue(mNumericSetting.getDoubleDefaultValue());
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java
new file mode 100644
index 0000000000..989f2b7cde
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.Settings;
+
+public class InputMappingIntSetting implements AbstractIntSetting
+{
+ private final NumericSetting mNumericSetting;
+
+ public InputMappingIntSetting(NumericSetting numericSetting)
+ {
+ mNumericSetting = numericSetting;
+ }
+
+ @Override
+ public int getInt(Settings settings)
+ {
+ return mNumericSetting.getIntValue();
+ }
+
+ @Override
+ public void setInt(Settings settings, int newValue)
+ {
+ mNumericSetting.setIntValue(newValue);
+ }
+
+ @Override
+ public boolean isOverridden(Settings settings)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isRuntimeEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean delete(Settings settings)
+ {
+ mNumericSetting.setIntValue(mNumericSetting.getIntDefaultValue());
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java
new file mode 100644
index 0000000000..5e4c0076ea
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model;
+
+import androidx.annotation.NonNull;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+
+public final class MappingCommon
+{
+ private MappingCommon()
+ {
+ }
+
+ /**
+ * Waits until the user presses one or more inputs or until a timeout,
+ * then returns the pressed inputs.
+ *
+ * When this is being called, a separate thread must be calling ControllerInterface's
+ * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered.
+ *
+ * @param controller The device to detect inputs from.
+ * @param allDevices Whether to also detect inputs from devices other than the specified one.
+ * @return The input(s) pressed by the user in the form of an InputCommon expression,
+ * or an empty string if there were no inputs.
+ */
+ public static native String detectInput(@NonNull EmulatedController controller,
+ boolean allDevices);
+
+ public static native String getExpressionForControl(String control, String device,
+ String defaultDevice);
+
+ public static native void save();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java
new file mode 100644
index 0000000000..6033e17e37
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
+
+import androidx.annotation.Keep;
+
+/**
+ * Represents a C++ ControllerEmu::Control.
+ *
+ * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
+ * in C++ is undefined behavior!
+ */
+public class Control
+{
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private Control(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ public native String getUiName();
+
+ public native ControlReference getControlReference();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java
new file mode 100644
index 0000000000..1bafd4a1f2
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
+
+import androidx.annotation.Keep;
+
+/**
+ * Represents a C++ ControllerEmu::ControlGroup.
+ *
+ * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
+ * in C++ is undefined behavior!
+ */
+public class ControlGroup
+{
+ public static final int TYPE_OTHER = 0;
+ public static final int TYPE_STICK = 1;
+ public static final int TYPE_MIXED_TRIGGERS = 2;
+ public static final int TYPE_BUTTONS = 3;
+ public static final int TYPE_FORCE = 4;
+ public static final int TYPE_ATTACHMENTS = 5;
+ public static final int TYPE_TILT = 6;
+ public static final int TYPE_CURSOR = 7;
+ public static final int TYPE_TRIGGERS = 8;
+ public static final int TYPE_SLIDER = 9;
+ public static final int TYPE_SHAKE = 10;
+ public static final int TYPE_IMU_ACCELEROMETER = 11;
+ public static final int TYPE_IMU_GYROSCOPE = 12;
+ public static final int TYPE_IMU_CURSOR = 13;
+
+ public static final int DEFAULT_ENABLED_ALWAYS = 0;
+ public static final int DEFAULT_ENABLED_YES = 1;
+ public static final int DEFAULT_ENABLED_NO = 2;
+
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private ControlGroup(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ public native String getUiName();
+
+ public native int getGroupType();
+
+ public native int getDefaultEnabledValue();
+
+ public native boolean getEnabled();
+
+ public native void setEnabled(boolean value);
+
+ public native int getControlCount();
+
+ public native Control getControl(int i);
+
+ public native int getNumericSettingCount();
+
+ public native NumericSetting getNumericSetting(int i);
+
+ /**
+ * If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting.
+ * Otherwise, undefined behavior!
+ */
+ public native NumericSetting getAttachmentSetting();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java
new file mode 100644
index 0000000000..66fd9fa02d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
+
+import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
+
+/**
+ * Represents a C++ ControlReference.
+ *
+ * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
+ * in C++ is undefined behavior!
+ */
+public class ControlReference
+{
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private ControlReference(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ public native double getState();
+
+ public native String getExpression();
+
+ /**
+ * Sets the expression for this control reference.
+ *
+ * @param expr The new expression
+ * @return null on success, a human-readable error on failure
+ */
+ @Nullable
+ public native String setExpression(String expr);
+
+ public native boolean isInput();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java
new file mode 100644
index 0000000000..39645ffbba
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
+
+import androidx.annotation.Keep;
+
+/**
+ * Represents a C++ ControllerEmu::EmulatedController.
+ *
+ * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
+ * in C++ is undefined behavior!
+ */
+public class EmulatedController
+{
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private EmulatedController(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ public native String getDefaultDevice();
+
+ public native void setDefaultDevice(String device);
+
+ public native int getGroupCount();
+
+ public native ControlGroup getGroup(int index);
+
+ public native void updateSingleControlReference(ControlReference controlReference);
+
+ public native void loadDefaultSettings();
+
+ public native void clearSettings();
+
+ public native void loadProfile(String path);
+
+ public native void saveProfile(String path);
+
+ public static native EmulatedController getGcPad(int controllerIndex);
+
+ public static native EmulatedController getWiimote(int controllerIndex);
+
+ public static native EmulatedController getWiimoteAttachment(int controllerIndex,
+ int attachmentIndex);
+
+ public static native int getSelectedWiimoteAttachment(int controllerIndex);
+
+ public static native NumericSetting getSidewaysWiimoteSetting(int controllerIndex);
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java
new file mode 100644
index 0000000000..bcb9dce181
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
+
+import androidx.annotation.Keep;
+
+/**
+ * Represents a C++ ControllerEmu::NumericSetting.
+ *
+ * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
+ * in C++ is undefined behavior!
+ */
+public class NumericSetting
+{
+ public static final int TYPE_INT = 0;
+ public static final int TYPE_DOUBLE = 1;
+ public static final int TYPE_BOOLEAN = 2;
+
+ @Keep
+ private final long mPointer;
+
+ @Keep
+ private NumericSetting(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ /**
+ * @return The name used in the UI.
+ */
+ public native String getUiName();
+
+ /**
+ * @return A string applied to the number in the UI (unit of measure).
+ */
+ public native String getUiSuffix();
+
+ /**
+ * @return Detailed description of the setting.
+ */
+ public native String getUiDescription();
+
+ /**
+ * @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN
+ */
+ public native int getType();
+
+ public native ControlReference getControlReference();
+
+ /**
+ * If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior!
+ */
+ public native int getIntValue();
+
+ /**
+ * If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior!
+ */
+ public native void setIntValue(int value);
+
+ /**
+ * If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior!
+ */
+ public native int getIntDefaultValue();
+
+ /**
+ * If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior!
+ */
+ public native double getDoubleValue();
+
+ /**
+ * If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior!
+ */
+ public native void setDoubleValue(double value);
+
+ /**
+ * If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior!
+ */
+ public native double getDoubleDefaultValue();
+
+ /**
+ * If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior!
+ */
+ public native double getDoubleMin();
+
+ /**
+ * If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior!
+ */
+ public native double getDoubleMax();
+
+ /**
+ * If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior!
+ */
+ public native boolean getBooleanValue();
+
+ /**
+ * If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior!
+ */
+ public native void setBooleanValue(boolean value);
+
+ /**
+ * If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior!
+ */
+ public native boolean getBooleanDefaultValue();
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java
new file mode 100644
index 0000000000..e27d0f57ae
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.view;
+
+import android.content.Context;
+
+import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+import org.dolphinemu.dolphinemu.features.settings.model.Settings;
+import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting;
+
+public class InputDeviceSetting extends StringSingleChoiceSetting
+{
+ private final EmulatedController mController;
+
+ public InputDeviceSetting(Context context, int titleId, int descriptionId,
+ EmulatedController controller)
+ {
+ super(context, null, titleId, descriptionId, null, null, null);
+
+ mController = controller;
+
+ refreshChoicesAndValues();
+ }
+
+ @Override
+ public String getSelectedChoice(Settings settings)
+ {
+ return mController.getDefaultDevice();
+ }
+
+ @Override
+ public String getSelectedValue(Settings settings)
+ {
+ return mController.getDefaultDevice();
+ }
+
+ @Override
+ public void setSelectedValue(Settings settings, String newValue)
+ {
+ mController.setDefaultDevice(newValue);
+ }
+
+ @Override
+ public void refreshChoicesAndValues()
+ {
+ String[] devices = ControllerInterface.getAllDeviceStrings();
+
+ mChoices = devices;
+ mValues = devices;
+ }
+
+ @Override
+ public boolean isEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean canClear()
+ {
+ return true;
+ }
+
+ @Override
+ public void clear(Settings settings)
+ {
+ setSelectedValue(settings, "");
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java
new file mode 100644
index 0000000000..86379bf5a6
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.model.view;
+
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
+
+public final class InputMappingControlSetting extends SettingsItem
+{
+ private final ControlReference mControlReference;
+ private final EmulatedController mController;
+
+ public InputMappingControlSetting(Control control, EmulatedController controller)
+ {
+ super(control.getUiName(), "");
+ mControlReference = control.getControlReference();
+ mController = controller;
+ }
+
+ public String getValue()
+ {
+ return mControlReference.getExpression();
+ }
+
+ public void setValue(String expr)
+ {
+ mControlReference.setExpression(expr);
+ mController.updateSingleControlReference(mControlReference);
+ }
+
+ public void clearValue()
+ {
+ setValue("");
+ }
+
+ @Override
+ public int getType()
+ {
+ return TYPE_INPUT_MAPPING_CONTROL;
+ }
+
+ @Override
+ public AbstractSetting getSetting()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isEditable()
+ {
+ return true;
+ }
+
+ public EmulatedController getController()
+ {
+ return mController;
+ }
+
+ public ControlReference getControlReference()
+ {
+ return mControlReference;
+ }
+
+ public boolean isInput()
+ {
+ return mControlReference.isInput();
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java
new file mode 100644
index 0000000000..0ad589fe40
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
+
+import java.util.function.Consumer;
+
+public final class AdvancedMappingControlAdapter
+ extends RecyclerView.Adapter<AdvancedMappingControlViewHolder>
+{
+ private final Consumer<String> mOnClickCallback;
+
+ private String[] mControls = new String[0];
+
+ public AdvancedMappingControlAdapter(Consumer<String> onClickCallback)
+ {
+ mOnClickCallback = onClickCallback;
+ }
+
+ @NonNull @Override
+ public AdvancedMappingControlViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+ int viewType)
+ {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+ ListItemAdvancedMappingControlBinding binding =
+ ListItemAdvancedMappingControlBinding.inflate(inflater);
+ return new AdvancedMappingControlViewHolder(binding, mOnClickCallback);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AdvancedMappingControlViewHolder holder, int position)
+ {
+ holder.bind(mControls[position]);
+ }
+
+ @Override
+ public int getItemCount()
+ {
+ return mControls.length;
+ }
+
+ public void setControls(String[] controls)
+ {
+ mControls = controls;
+ notifyDataSetChanged();
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java
new file mode 100644
index 0000000000..2ce925235b
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
+
+import java.util.function.Consumer;
+
+public class AdvancedMappingControlViewHolder extends RecyclerView.ViewHolder
+{
+ private final ListItemAdvancedMappingControlBinding mBinding;
+
+ private String mName;
+
+ public AdvancedMappingControlViewHolder(@NonNull ListItemAdvancedMappingControlBinding binding,
+ Consumer<String> onClickCallback)
+ {
+ super(binding.getRoot());
+
+ mBinding = binding;
+
+ binding.getRoot().setOnClickListener(view -> onClickCallback.accept(mName));
+ }
+
+ public void bind(String name)
+ {
+ mName = name;
+
+ mBinding.textName.setText(name);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java
new file mode 100644
index 0000000000..de8e8265a6
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.google.android.material.divider.MaterialDividerItemDecoration;
+
+import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding;
+import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
+import org.dolphinemu.dolphinemu.features.input.model.CoreDevice;
+import org.dolphinemu.dolphinemu.features.input.model.MappingCommon;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+public final class AdvancedMappingDialog extends AlertDialog
+ implements AdapterView.OnItemClickListener
+{
+ private final DialogAdvancedMappingBinding mBinding;
+ private final ControlReference mControlReference;
+ private final EmulatedController mController;
+ private final String[] mDevices;
+ private final AdvancedMappingControlAdapter mControlAdapter;
+
+ private String mSelectedDevice;
+
+ public AdvancedMappingDialog(Context context, DialogAdvancedMappingBinding binding,
+ ControlReference controlReference, EmulatedController controller)
+ {
+ super(context);
+
+ mBinding = binding;
+ mControlReference = controlReference;
+ mController = controller;
+
+ mDevices = ControllerInterface.getAllDeviceStrings();
+
+ // TODO: Remove workaround for text filtering issue in material components when fixed
+ // https://github.com/material-components/material-components-android/issues/1464
+ mBinding.dropdownDevice.setSaveEnabled(false);
+
+ binding.dropdownDevice.setOnItemClickListener(this);
+
+ ArrayAdapter<String> deviceAdapter = new ArrayAdapter<>(
+ context, android.R.layout.simple_spinner_dropdown_item, mDevices);
+ binding.dropdownDevice.setAdapter(deviceAdapter);
+
+ mControlAdapter = new AdvancedMappingControlAdapter(this::onControlClicked);
+ mBinding.listControl.setAdapter(mControlAdapter);
+ mBinding.listControl.setLayoutManager(new LinearLayoutManager(context));
+
+ MaterialDividerItemDecoration divider =
+ new MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL);
+ divider.setLastItemDecorated(false);
+ mBinding.listControl.addItemDecoration(divider);
+
+ binding.editExpression.setText(controlReference.getExpression());
+
+ selectDefaultDevice();
+ }
+
+ public String getExpression()
+ {
+ return mBinding.editExpression.getText().toString();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id)
+ {
+ setSelectedDevice(mDevices[position]);
+ }
+
+ private void setSelectedDevice(String deviceString)
+ {
+ mSelectedDevice = deviceString;
+
+ CoreDevice device = ControllerInterface.getDevice(deviceString);
+ if (device == null)
+ setControls(new CoreDevice.Control[0]);
+ else if (mControlReference.isInput())
+ setControls(device.getInputs());
+ else
+ setControls(device.getOutputs());
+ }
+
+ private void setControls(CoreDevice.Control[] controls)
+ {
+ mControlAdapter.setControls(
+ Arrays.stream(controls)
+ .map(CoreDevice.Control::getName)
+ .toArray(String[]::new));
+ }
+
+ private void onControlClicked(String control)
+ {
+ String expression = MappingCommon.getExpressionForControl(control, mSelectedDevice,
+ mController.getDefaultDevice());
+
+ int start = Math.max(mBinding.editExpression.getSelectionStart(), 0);
+ int end = Math.max(mBinding.editExpression.getSelectionEnd(), 0);
+ mBinding.editExpression.getText().replace(
+ Math.min(start, end), Math.max(start, end), expression, 0, expression.length());
+ }
+
+ private void selectDefaultDevice()
+ {
+ String defaultDevice = mController.getDefaultDevice();
+ boolean isInput = mControlReference.isInput();
+
+ if (Arrays.asList(mDevices).contains(defaultDevice) &&
+ (isInput || deviceHasOutputs(defaultDevice)))
+ {
+ // The default device is available, and it's an appropriate choice. Pick it
+ setSelectedDevice(defaultDevice);
+ mBinding.dropdownDevice.setText(defaultDevice, false);
+ return;
+ }
+ else if (!isInput)
+ {
+ // Find the first device that has an output. (Most built-in devices don't have any)
+ Optional<String> deviceWithOutputs = Arrays.stream(mDevices)
+ .filter(AdvancedMappingDialog::deviceHasOutputs)
+ .findFirst();
+
+ if (deviceWithOutputs.isPresent())
+ {
+ setSelectedDevice(deviceWithOutputs.get());
+ mBinding.dropdownDevice.setText(deviceWithOutputs.get(), false);
+ return;
+ }
+ }
+
+ // Nothing found
+ setSelectedDevice("");
+ }
+
+ private static boolean deviceHasOutputs(String deviceString)
+ {
+ CoreDevice device = ControllerInterface.getDevice(deviceString);
+ return device != null && device.getOutputs().length > 0;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java
new file mode 100644
index 0000000000..9872bdd01e
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.app.Activity;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
+import org.dolphinemu.dolphinemu.features.input.model.MappingCommon;
+import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
+
+/**
+ * {@link AlertDialog} derivative that listens for
+ * motion events from controllers and joysticks.
+ */
+public final class MotionAlertDialog extends AlertDialog
+{
+ private final Activity mActivity;
+ private final InputMappingControlSetting mSetting;
+ private final boolean mAllDevices;
+ private boolean mRunning = false;
+
+ /**
+ * Constructor
+ *
+ * @param activity The current {@link Activity}.
+ * @param setting The setting to show this dialog for.
+ * @param allDevices Whether to detect inputs from devices other than the configured one.
+ */
+ public MotionAlertDialog(Activity activity, InputMappingControlSetting setting,
+ boolean allDevices)
+ {
+ super(activity);
+
+ mActivity = activity;
+ mSetting = setting;
+ mAllDevices = allDevices;
+ }
+
+ @Override
+ protected void onStart()
+ {
+ super.onStart();
+
+ mRunning = true;
+ new Thread(() ->
+ {
+ String result = MappingCommon.detectInput(mSetting.getController(), mAllDevices);
+ mActivity.runOnUiThread(() ->
+ {
+ if (mRunning)
+ {
+ mSetting.setValue(result);
+ dismiss();
+ }
+ });
+ }).start();
+ }
+
+ @Override
+ protected void onStop()
+ {
+ super.onStop();
+ mRunning = false;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event)
+ {
+ ControllerInterface.dispatchKeyEvent(event);
+
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isLongPress())
+ {
+ // Special case: Let the user cancel by long-pressing Back (intended for non-touch devices)
+ mSetting.clearValue();
+ dismiss();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event)
+ {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
+ {
+ // Special case: Let the user cancel by touching an on-screen button
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ ControllerInterface.dispatchGenericMotionEvent(event);
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java
new file mode 100644
index 0000000000..9908d6f5fd
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
+
+public final class ProfileAdapter extends RecyclerView.Adapter<ProfileViewHolder>
+{
+ private final Context mContext;
+ private final ProfileDialogPresenter mPresenter;
+
+ private final String[] mStockProfileNames;
+ private final String[] mUserProfileNames;
+
+ public ProfileAdapter(Context context, ProfileDialogPresenter presenter)
+ {
+ mContext = context;
+ mPresenter = presenter;
+
+ mStockProfileNames = presenter.getProfileNames(true);
+ mUserProfileNames = presenter.getProfileNames(false);
+ }
+
+ @NonNull @Override
+ public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
+ {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+ ListItemProfileBinding binding = ListItemProfileBinding.inflate(inflater, parent, false);
+ return new ProfileViewHolder(mPresenter, binding);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position)
+ {
+ if (position < mStockProfileNames.length)
+ {
+ holder.bind(mStockProfileNames[position], true);
+ return;
+ }
+
+ position -= mStockProfileNames.length;
+
+ if (position < mUserProfileNames.length)
+ {
+ holder.bind(mUserProfileNames[position], false);
+ return;
+ }
+
+ holder.bindAsEmpty(mContext);
+ }
+
+ @Override
+ public int getItemCount()
+ {
+ return mStockProfileNames.length + mUserProfileNames.length + 1;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt
new file mode 100644
index 0000000000..2779ee7473
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui
+
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.databinding.DialogInputProfilesBinding
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+
+class ProfileDialog : BottomSheetDialogFragment() {
+ private var presenter: ProfileDialogPresenter? = null
+
+ private var _binding: DialogInputProfilesBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ val menuTag: MenuTag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ requireArguments().getSerializable(KEY_MENU_TAG, MenuTag::class.java) as MenuTag
+ } else {
+ requireArguments().getSerializable(KEY_MENU_TAG) as MenuTag
+ }
+
+ presenter = ProfileDialogPresenter(this, menuTag)
+
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = DialogInputProfilesBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ binding.profileList.adapter = ProfileAdapter(context, presenter)
+ binding.profileList.layoutManager = LinearLayoutManager(context)
+ val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL)
+ divider.isLastItemDecorated = false
+ binding.profileList.addItemDecoration(divider)
+
+ // You can't expand a bottom sheet with a controller/remote/other non-touch devices
+ val behavior: BottomSheetBehavior<View> = BottomSheetBehavior.from(view.parent as View)
+ if (!resources.getBoolean(R.bool.hasTouch)) {
+ behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ companion object {
+ private const val KEY_MENU_TAG = "menu_tag"
+
+ @JvmStatic
+ fun create(menuTag: MenuTag): ProfileDialog {
+ val dialog = ProfileDialog()
+ val args = Bundle()
+ args.putSerializable(KEY_MENU_TAG, menuTag)
+ dialog.arguments = args
+ return dialog
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java
new file mode 100644
index 0000000000..667cc9017c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.textfield.TextInputEditText;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
+import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView;
+import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.Arrays;
+
+public final class ProfileDialogPresenter
+{
+ private static final String EXTENSION = ".ini";
+
+ private final Context mContext;
+ private final DialogFragment mDialog;
+ private final MenuTag mMenuTag;
+
+ public ProfileDialogPresenter(MenuTag menuTag)
+ {
+ mContext = null;
+ mDialog = null;
+ mMenuTag = menuTag;
+ }
+
+ public ProfileDialogPresenter(DialogFragment dialog, MenuTag menuTag)
+ {
+ mContext = dialog.getContext();
+ mDialog = dialog;
+ mMenuTag = menuTag;
+ }
+
+ public String[] getProfileNames(boolean stock)
+ {
+ File[] profiles = new File(getProfileDirectoryPath(stock)).listFiles(
+ file -> !file.isDirectory() && file.getName().endsWith(EXTENSION));
+
+ if (profiles == null)
+ return new String[0];
+
+ return Arrays.stream(profiles)
+ .map(file -> file.getName().substring(0, file.getName().length() - EXTENSION.length()))
+ .sorted(Collator.getInstance())
+ .toArray(String[]::new);
+ }
+
+ public void loadProfile(@NonNull String profileName, boolean stock)
+ {
+ new MaterialAlertDialogBuilder(mContext)
+ .setMessage(mContext.getString(R.string.input_profile_confirm_load, profileName))
+ .setPositiveButton(R.string.yes, (dialogInterface, i) ->
+ {
+ mMenuTag.getCorrespondingEmulatedController()
+ .loadProfile(getProfilePath(profileName, stock));
+ ((SettingsActivityView) mDialog.requireActivity()).onControllerSettingsChanged();
+ mDialog.dismiss();
+ })
+ .setNegativeButton(R.string.no, null)
+ .show();
+ }
+
+ public void saveProfile(@NonNull String profileName)
+ {
+ // If the user is saving over an existing profile, we should show an overwrite warning.
+ // If the user is creating a new profile, we normally shouldn't show a warning,
+ // but if they've entered the name of an existing profile, we should shown an overwrite warning.
+
+ String profilePath = getProfilePath(profileName, false);
+ if (!new File(profilePath).exists())
+ {
+ mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
+ mDialog.dismiss();
+ }
+ else
+ {
+ new MaterialAlertDialogBuilder(mContext)
+ .setMessage(mContext.getString(R.string.input_profile_confirm_save, profileName))
+ .setPositiveButton(R.string.yes, (dialogInterface, i) ->
+ {
+ mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
+ mDialog.dismiss();
+ })
+ .setNegativeButton(R.string.no, null)
+ .show();
+ }
+ }
+
+ public void saveProfileAndPromptForName()
+ {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater);
+ TextInputEditText input = binding.input;
+
+ new MaterialAlertDialogBuilder(mContext)
+ .setView(binding.getRoot())
+ .setPositiveButton(R.string.ok, (dialogInterface, i) ->
+ saveProfile(input.getText().toString()))
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ }
+
+ public void deleteProfile(@NonNull String profileName)
+ {
+ new MaterialAlertDialogBuilder(mContext)
+ .setMessage(mContext.getString(R.string.input_profile_confirm_delete, profileName))
+ .setPositiveButton(R.string.yes, (dialogInterface, i) ->
+ {
+ new File(getProfilePath(profileName, false)).delete();
+ mDialog.dismiss();
+ })
+ .setNegativeButton(R.string.no, null)
+ .show();
+ }
+
+ private String getProfileDirectoryName()
+ {
+ if (mMenuTag.isGCPadMenu())
+ return "GCPad";
+ else if (mMenuTag.isWiimoteMenu())
+ return "Wiimote";
+ else
+ throw new UnsupportedOperationException();
+ }
+
+ private String getProfileDirectoryPath(boolean stock)
+ {
+ if (stock)
+ {
+ return DirectoryInitialization.getSysDirectory() + "/Profiles/" + getProfileDirectoryName() +
+ '/';
+ }
+ else
+ {
+ return DirectoryInitialization.getUserDirectory() + "/Config/Profiles/" +
+ getProfileDirectoryName() + '/';
+ }
+ }
+
+ private String getProfilePath(String profileName, boolean stock)
+ {
+ return getProfileDirectoryPath(stock) + profileName + EXTENSION;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java
new file mode 100644
index 0000000000..d4d40c0c65
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
+
+public class ProfileViewHolder extends RecyclerView.ViewHolder
+{
+ private final ProfileDialogPresenter mPresenter;
+ private final ListItemProfileBinding mBinding;
+
+ private String mProfileName;
+ private boolean mStock;
+
+ public ProfileViewHolder(@NonNull ProfileDialogPresenter presenter,
+ @NonNull ListItemProfileBinding binding)
+ {
+ super(binding.getRoot());
+
+ mPresenter = presenter;
+ mBinding = binding;
+
+ binding.buttonLoad.setOnClickListener(view -> loadProfile());
+ binding.buttonSave.setOnClickListener(view -> saveProfile());
+ binding.buttonDelete.setOnClickListener(view -> deleteProfile());
+ }
+
+ public void bind(String profileName, boolean stock)
+ {
+ mProfileName = profileName;
+ mStock = stock;
+
+ mBinding.textName.setText(profileName);
+
+ mBinding.buttonLoad.setVisibility(View.VISIBLE);
+ mBinding.buttonSave.setVisibility(stock ? View.GONE : View.VISIBLE);
+ mBinding.buttonDelete.setVisibility(stock ? View.GONE : View.VISIBLE);
+ }
+
+ public void bindAsEmpty(Context context)
+ {
+ mProfileName = null;
+ mStock = false;
+
+ mBinding.textName.setText(context.getText(R.string.input_profile_new));
+
+ mBinding.buttonLoad.setVisibility(View.GONE);
+ mBinding.buttonSave.setVisibility(View.VISIBLE);
+ mBinding.buttonDelete.setVisibility(View.GONE);
+ }
+
+ private void loadProfile()
+ {
+ mPresenter.loadProfile(mProfileName, mStock);
+ }
+
+ private void saveProfile()
+ {
+ if (mProfileName == null)
+ mPresenter.saveProfileAndPromptForName();
+ else
+ mPresenter.saveProfile(mProfileName);
+ }
+
+ private void deleteProfile()
+ {
+ mPresenter.deleteProfile(mProfileName);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java
new file mode 100644
index 0000000000..fd4b92cf95
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.input.ui.viewholder;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding;
+import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
+import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
+import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
+import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder;
+
+public final class InputMappingControlSettingViewHolder extends SettingViewHolder
+{
+ private InputMappingControlSetting mItem;
+
+ private final ListItemMappingBinding mBinding;
+
+ public InputMappingControlSettingViewHolder(@NonNull ListItemMappingBinding binding,
+ SettingsAdapter adapter)
+ {
+ super(binding.getRoot(), adapter);
+ mBinding = binding;
+ }
+
+ @Override
+ public void bind(SettingsItem item)
+ {
+ mItem = (InputMappingControlSetting) item;
+
+ mBinding.textSettingName.setText(mItem.getName());
+ mBinding.textSettingDescription.setText(mItem.getValue());
+ mBinding.buttonAdvancedSettings.setOnClickListener(this::onLongClick);
+
+ setStyle(mBinding.textSettingName, mItem);
+ }
+
+ @Override
+ public void onClick(View clicked)
+ {
+ if (!mItem.isEditable())
+ {
+ showNotRuntimeEditableError();
+ return;
+ }
+
+ if (mItem.isInput())
+ getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition());
+ else
+ getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition());
+
+ setStyle(mBinding.textSettingName, mItem);
+ }
+
+ @Override
+ public boolean onLongClick(View clicked)
+ {
+ if (!mItem.isEditable())
+ {
+ showNotRuntimeEditableError();
+ return true;
+ }
+
+ getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition());
+
+ return true;
+ }
+
+ @Nullable @Override
+ protected SettingsItem getItem()
+ {
+ return mItem;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
index 1194e1f56a..cbaa7a4292 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java
@@ -100,8 +100,6 @@ public enum BooleanSetting implements AbstractBooleanSetting
"UseBlackBackgrounds", false),
MAIN_JOYSTICK_REL_CENTER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
"JoystickRelCenter", true),
- MAIN_PHONE_RUMBLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
- "PhoneRumble", true),
MAIN_SHOW_INPUT_OVERLAY(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
"ShowInputOverlay", true),
MAIN_IR_ALWAYS_RECENTER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java
index a51eb71749..77cf7b339a 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java
@@ -33,6 +33,10 @@ public enum IntSetting implements AbstractIntSetting
MAIN_AUDIO_VOLUME(Settings.FILE_DOLPHIN, Settings.SECTION_INI_DSP, "Volume", 100),
+ MAIN_OVERLAY_GC_CONTROLLER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
+ "OverlayGCController", 0), // Defaults to GameCube controller 1
+ MAIN_OVERLAY_WII_CONTROLLER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
+ "OverlayWiiController", 4), // Defaults to Wii Remote 1
MAIN_CONTROL_SCALE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "ControlScale", 50),
MAIN_CONTROL_OPACITY(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "ControlOpacity", 65),
MAIN_EMULATION_ORIENTATION(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
@@ -41,7 +45,6 @@ public enum IntSetting implements AbstractIntSetting
MAIN_INTERFACE_THEME_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
"InterfaceThemeMode", -1),
MAIN_LAST_PLATFORM_TAB(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "LastPlatformTab", 0),
- MAIN_MOTION_CONTROLS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "MotionControls", 1),
MAIN_IR_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "IRMode",
InputOverlayPointer.MODE_FOLLOW),
@@ -189,4 +192,16 @@ public enum IntSetting implements AbstractIntSetting
{
NativeConfig.setInt(layer, mFile, mSection, mKey, newValue);
}
+
+ public static IntSetting getSettingForSIDevice(int channel)
+ {
+ return new IntSetting[]{MAIN_SI_DEVICE_0, MAIN_SI_DEVICE_1, MAIN_SI_DEVICE_2, MAIN_SI_DEVICE_3}
+ [channel];
+ }
+
+ public static IntSetting getSettingForWiimoteSource(int index)
+ {
+ return new IntSetting[]{WIIMOTE_1_SOURCE, WIIMOTE_2_SOURCE, WIIMOTE_3_SOURCE, WIIMOTE_4_SOURCE,
+ WIIMOTE_BB_SOURCE}[index];
+ }
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java
deleted file mode 100644
index 0b92cba802..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.model;
-
-import androidx.annotation.NonNull;
-
-public class LegacyFloatSetting extends AbstractLegacySetting implements AbstractFloatSetting
-{
- private final float mDefaultValue;
-
- public LegacyFloatSetting(String file, String section, String key, float defaultValue)
- {
- super(file, section, key);
- mDefaultValue = defaultValue;
- }
-
- @Override
- public float getFloat(@NonNull Settings settings)
- {
- return settings.getSection(mFile, mSection).getFloat(mKey, mDefaultValue);
- }
-
- @Override
- public void setFloat(@NonNull Settings settings, float newValue)
- {
- settings.getSection(mFile, mSection).setFloat(mKey, newValue);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java
index ebdaf70691..fb5e16965b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java
@@ -8,6 +8,7 @@ import android.widget.Toast;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.features.input.model.MappingCommon;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView;
import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
@@ -24,7 +25,6 @@ public class Settings implements Closeable
public static final String FILE_SYSCONF = "SYSCONF";
public static final String FILE_GFX = "GFX";
public static final String FILE_LOGGER = "Logger";
- public static final String FILE_GCPAD = "GCPadNew";
public static final String FILE_WIIMOTE = "WiimoteNew";
public static final String SECTION_INI_ANDROID = "Android";
@@ -46,10 +46,7 @@ public class Settings implements Closeable
public static final String SECTION_STEREOSCOPY = "Stereoscopy";
- public static final String SECTION_WIIMOTE = "Wiimote";
-
public static final String SECTION_BINDINGS = "Android";
- public static final String SECTION_CONTROLS = "Controls";
public static final String SECTION_PROFILE = "Profile";
public static final String SECTION_ANALYTICS = "Analytics";
@@ -64,7 +61,6 @@ public class Settings implements Closeable
FILE_WIIMOTE};
private Map<String, IniFile> mIniFiles = new HashMap<>();
- private final Map<String, IniFile> mWiimoteProfileFiles = new HashMap<>();
private boolean mLoadedRecursiveIsoPathsValue = false;
@@ -99,50 +95,6 @@ public class Settings implements Closeable
return mIsWii;
}
- public IniFile getWiimoteProfile(String profile, int padID)
- {
- IniFile wiimoteProfileIni = mWiimoteProfileFiles.computeIfAbsent(profile, profileComputed ->
- {
- IniFile newIni = new IniFile();
- newIni.load(SettingsFile.getWiiProfile(profileComputed), false);
- return newIni;
- });
-
- if (!wiimoteProfileIni.exists(SECTION_PROFILE))
- {
- String defaultWiiProfilePath = DirectoryInitialization.getUserDirectory() +
- "/Config/Profiles/Wiimote/WiimoteProfile.ini";
-
- wiimoteProfileIni.load(defaultWiiProfilePath, false);
-
- wiimoteProfileIni
- .setString(SECTION_PROFILE, "Device", "Android/" + (padID + 4) + "/Touchscreen");
- }
-
- return wiimoteProfileIni;
- }
-
- public void enableWiimoteProfile(Settings settings, String profile, String profileKey)
- {
- getWiimoteControlsSection(settings).setString(profileKey, profile);
- }
-
- public boolean disableWiimoteProfile(Settings settings, String profileKey)
- {
- return getWiimoteControlsSection(settings).delete(profileKey);
- }
-
- public boolean isWiimoteProfileEnabled(Settings settings, String profile,
- String profileKey)
- {
- return profile.equals(getWiimoteControlsSection(settings).getString(profileKey, ""));
- }
-
- private IniFile.Section getWiimoteControlsSection(Settings settings)
- {
- return settings.getSection(GAME_SETTINGS_PLACEHOLDER_FILE_NAME, SECTION_CONTROLS);
- }
-
public int getWriteLayer()
{
return isGameSpecific() ? NativeConfig.LAYER_LOCAL_GAME : NativeConfig.LAYER_BASE_OR_CURRENT;
@@ -218,13 +170,14 @@ public class Settings implements Closeable
SettingsFile.saveFile(entry.getKey(), entry.getValue(), view);
}
+ MappingCommon.save();
+
NativeConfig.save(NativeConfig.LAYER_BASE);
if (!NativeLibrary.IsRunning())
{
// Notify the native code of the changes to legacy settings
NativeLibrary.ReloadConfig();
- NativeLibrary.ReloadWiimoteConfig();
}
// LogManager does use the new config system, but doesn't pick up on changes automatically
@@ -251,11 +204,6 @@ public class Settings implements Closeable
NativeConfig.save(NativeConfig.LAYER_LOCAL_GAME);
}
-
- for (Map.Entry<String, IniFile> entry : mWiimoteProfileFiles.entrySet())
- {
- entry.getValue().save(SettingsFile.getWiiProfile(entry.getKey()));
- }
}
public void clearSettings()
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java
deleted file mode 100644
index 1705e969c4..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.model;
-
-import androidx.annotation.NonNull;
-
-import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
-
-// This stuff is pretty ugly. It's a kind of workaround for certain controller settings
-// not actually being available as game-specific settings.
-public class WiimoteProfileBooleanSetting implements AbstractBooleanSetting
-{
- private final int mPadID;
-
- private final String mSection;
- private final String mKey;
- private final boolean mDefaultValue;
-
- private final String mProfileKey;
-
- private final String mProfile;
-
- public WiimoteProfileBooleanSetting(String gameID, int padID, String section, String key,
- boolean defaultValue)
- {
- mPadID = padID;
- mSection = section;
- mKey = key;
- mDefaultValue = defaultValue;
-
- mProfileKey = SettingsFile.KEY_WIIMOTE_PROFILE + (padID + 1);
-
- mProfile = gameID + "_Wii" + padID;
- }
-
- @Override
- public boolean isOverridden(@NonNull Settings settings)
- {
- return settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey);
- }
-
- @Override
- public boolean isRuntimeEditable()
- {
- return false;
- }
-
- @Override
- public boolean delete(@NonNull Settings settings)
- {
- return settings.disableWiimoteProfile(settings, mProfileKey);
- }
-
- @Override
- public boolean getBoolean(@NonNull Settings settings)
- {
- if (settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey))
- return settings.getWiimoteProfile(mProfile, mPadID).getBoolean(mSection, mKey, mDefaultValue);
- else
- return mDefaultValue;
- }
-
- @Override
- public void setBoolean(@NonNull Settings settings, boolean newValue)
- {
- settings.getWiimoteProfile(mProfile, mPadID).setBoolean(mSection, mKey, newValue);
-
- settings.enableWiimoteProfile(settings, mProfile, mProfileKey);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java
deleted file mode 100644
index c62df5638d..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.model;
-
-import androidx.annotation.NonNull;
-
-import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
-
-// This stuff is pretty ugly. It's a kind of workaround for certain controller settings
-// not actually being available as game-specific settings.
-public class WiimoteProfileStringSetting implements AbstractStringSetting
-{
- private final int mPadID;
-
- private final String mSection;
- private final String mKey;
- private final String mDefaultValue;
-
- private final String mProfileKey;
-
- private final String mProfile;
-
- public WiimoteProfileStringSetting(String gameID, int padID, String section, String key,
- String defaultValue)
- {
- mPadID = padID;
- mSection = section;
- mKey = key;
- mDefaultValue = defaultValue;
-
- mProfileKey = SettingsFile.KEY_WIIMOTE_PROFILE + (padID + 1);
-
- mProfile = gameID + "_Wii" + padID;
- }
-
- @Override
- public boolean isOverridden(@NonNull Settings settings)
- {
- return settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey);
- }
-
- @Override
- public boolean isRuntimeEditable()
- {
- return false;
- }
-
- @Override
- public boolean delete(@NonNull Settings settings)
- {
- return settings.disableWiimoteProfile(settings, mProfileKey);
- }
-
- @NonNull @Override
- public String getString(@NonNull Settings settings)
- {
- if (settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey))
- return settings.getWiimoteProfile(mProfile, mPadID).getString(mSection, mKey, mDefaultValue);
- else
- return mDefaultValue;
- }
-
- @Override
- public void setString(@NonNull Settings settings, @NonNull String newValue)
- {
- settings.getWiimoteProfile(mProfile, mPadID).setString(mSection, mKey, newValue);
-
- settings.enableWiimoteProfile(settings, mProfile, mProfileKey);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java
index 167c5a471b..5b91517f75 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java
@@ -19,6 +19,13 @@ public class FloatSliderSetting extends SliderSetting
mSetting = setting;
}
+ public FloatSliderSetting(AbstractFloatSetting setting, CharSequence name,
+ CharSequence description, int min, int max, String units)
+ {
+ super(name, description, min, max, units);
+ mSetting = setting;
+ }
+
public int getSelectedValue(Settings settings)
{
return Math.round(mSetting.getFloat(settings));
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java
index 14c5f49021..b65f45837b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java
@@ -13,6 +13,11 @@ public class HeaderSetting extends SettingsItem
super(context, titleId, descriptionId);
}
+ public HeaderSetting(CharSequence title, CharSequence description)
+ {
+ super(title, description);
+ }
+
@Override
public int getType()
{
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java
deleted file mode 100644
index 175986d3ba..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.model.view;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-
-import androidx.preference.PreferenceManager;
-
-import org.dolphinemu.dolphinemu.DolphinApplication;
-import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.Settings;
-
-public class InputBindingSetting extends SettingsItem
-{
- private String mFile;
- private String mSection;
- private String mKey;
-
- private String mGameId;
-
- public InputBindingSetting(Context context, String file, String section, String key, int titleId,
- String gameId)
- {
- super(context, titleId, 0);
- mFile = file;
- mSection = section;
- mKey = key;
- mGameId = gameId;
- }
-
- public String getKey()
- {
- return mKey;
- }
-
- public String getValue(Settings settings)
- {
- return settings.getSection(mFile, mSection).getString(mKey, "");
- }
-
- /**
- * Saves the provided key input setting both to the INI file (so native code can use it) and as
- * an Android preference (so it persists correctly and is human-readable.)
- *
- * @param keyEvent KeyEvent of this key press.
- */
- public void onKeyInput(Settings settings, KeyEvent keyEvent)
- {
- InputDevice device = keyEvent.getDevice();
- String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode();
- String uiString = device.getName() + ": Button " + keyEvent.getKeyCode();
- setValue(settings, bindStr, uiString);
- }
-
- /**
- * Saves the provided motion input setting both to the INI file (so native code can use it) and as
- * an Android preference (so it persists correctly and is human-readable.)
- *
- * @param device InputDevice from which the input event originated.
- * @param motionRange MotionRange of the movement
- * @param axisDir Either '-' or '+'
- */
- public void onMotionInput(Settings settings, InputDevice device,
- InputDevice.MotionRange motionRange, char axisDir)
- {
- String bindStr =
- "Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir;
- String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir;
- setValue(settings, bindStr, uiString);
- }
-
- public void setValue(Settings settings, String bind, String ui)
- {
- SharedPreferences
- preferences =
- PreferenceManager.getDefaultSharedPreferences(DolphinApplication.getAppContext());
- SharedPreferences.Editor editor = preferences.edit();
- editor.putString(mKey + mGameId, ui);
- editor.apply();
-
- settings.getSection(mFile, mSection).setString(mKey, bind);
- }
-
- public void clearValue(Settings settings)
- {
- setValue(settings, "", "");
- }
-
- @Override
- public int getType()
- {
- return TYPE_INPUT_BINDING;
- }
-
- public String getGameId()
- {
- return mGameId;
- }
-
- @Override
- public AbstractSetting getSetting()
- {
- return null;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java
deleted file mode 100644
index 6c3e11e17b..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.model.view;
-
-import android.content.Context;
-import android.os.Vibrator;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-
-import org.dolphinemu.dolphinemu.DolphinApplication;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.Settings;
-import org.dolphinemu.dolphinemu.utils.Rumble;
-
-public class RumbleBindingSetting extends InputBindingSetting
-{
- public RumbleBindingSetting(Context context, String file, String section, String key, int titleId,
- String gameId)
- {
- super(context, file, section, key, titleId, gameId);
- }
-
- /**
- * Just need the device when saving rumble.
- */
- @Override
- public void onKeyInput(Settings settings, KeyEvent keyEvent)
- {
- saveRumble(settings, keyEvent.getDevice());
- }
-
- /**
- * Just need the device when saving rumble.
- */
- @Override
- public void onMotionInput(Settings settings, InputDevice device,
- InputDevice.MotionRange motionRange, char axisDir)
- {
- saveRumble(settings, device);
- }
-
- private void saveRumble(Settings settings, InputDevice device)
- {
- Vibrator vibrator = device.getVibrator();
- if (vibrator != null && vibrator.hasVibrator())
- {
- setValue(settings, device.getDescriptor(), device.getName());
- Rumble.doRumble(vibrator);
- }
- else
- {
- setValue(settings, "",
- DolphinApplication.getAppContext().getString(R.string.rumble_not_found));
- }
- }
-
- @Override
- public int getType()
- {
- return TYPE_RUMBLE_BINDING;
- }
-
- @Override
- public AbstractSetting getSetting()
- {
- return null;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java
index 7b41c8e3f8..6f06677be3 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java
@@ -21,9 +21,8 @@ public abstract class SettingsItem
public static final int TYPE_SINGLE_CHOICE = 2;
public static final int TYPE_SLIDER = 3;
public static final int TYPE_SUBMENU = 4;
- public static final int TYPE_INPUT_BINDING = 5;
+ public static final int TYPE_INPUT_MAPPING_CONTROL = 5;
public static final int TYPE_STRING_SINGLE_CHOICE = 6;
- public static final int TYPE_RUMBLE_BINDING = 7;
public static final int TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS = 8;
public static final int TYPE_FILE_PICKER = 9;
public static final int TYPE_RUN_RUNNABLE = 10;
@@ -96,6 +95,11 @@ public abstract class SettingsItem
return getSetting() != null;
}
+ public boolean canClear()
+ {
+ return hasSetting();
+ }
+
public void clear(Settings settings)
{
getSetting().delete(settings);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java
index 48961839c0..e63fde3bad 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java
@@ -23,6 +23,14 @@ public abstract class SliderSetting extends SettingsItem
mStepSize = stepSize;
}
+ public SliderSetting(CharSequence name, CharSequence description, int min, int max, String units)
+ {
+ super(name, description);
+ mMin = min;
+ mMax = max;
+ mUnits = units;
+ }
+
public abstract int getSelectedValue(Settings settings);
public int getMin()
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java
index df7ee6f4e9..1a9e237728 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java
@@ -12,11 +12,12 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
public class StringSingleChoiceSetting extends SettingsItem
{
- private AbstractStringSetting mSetting;
+ private final AbstractStringSetting mSetting;
- private String[] mChoices;
- private String[] mValues;
- private MenuTag mMenuTag;
+ protected String[] mChoices;
+ protected String[] mValues;
+ private final MenuTag mMenuTag;
+ private int mNoChoicesAvailableString = 0;
public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId,
int descriptionId, String[] choices, String[] values, MenuTag menuTag)
@@ -35,6 +36,13 @@ public class StringSingleChoiceSetting extends SettingsItem
}
public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId,
+ int descriptionId, String[] choices, String[] values, int noChoicesAvailableString)
+ {
+ this(context, setting, titleId, descriptionId, choices, values, null);
+ mNoChoicesAvailableString = noChoicesAvailableString;
+ }
+
+ public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId,
int descriptionId, int choicesId, int valuesId, MenuTag menuTag)
{
super(context, titleId, descriptionId);
@@ -60,6 +68,19 @@ public class StringSingleChoiceSetting extends SettingsItem
return mValues;
}
+ public String getChoiceAt(int index)
+ {
+ if (mChoices == null)
+ return null;
+
+ if (index >= 0 && index < mChoices.length)
+ {
+ return mChoices[index];
+ }
+
+ return "";
+ }
+
public String getValueAt(int index)
{
if (mValues == null)
@@ -73,6 +94,11 @@ public class StringSingleChoiceSetting extends SettingsItem
return "";
}
+ public String getSelectedChoice(Settings settings)
+ {
+ return getChoiceAt(getSelectedValueIndex(settings));
+ }
+
public String getSelectedValue(Settings settings)
{
return mSetting.getString(settings);
@@ -97,11 +123,20 @@ public class StringSingleChoiceSetting extends SettingsItem
return mMenuTag;
}
+ public int getNoChoicesAvailableString()
+ {
+ return mNoChoicesAvailableString;
+ }
+
public void setSelectedValue(Settings settings, String selection)
{
mSetting.setString(settings, selection);
}
+ public void refreshChoicesAndValues()
+ {
+ }
+
@Override
public int getType()
{
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java
index 6fc30a381b..a0a67dd838 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java
@@ -4,6 +4,8 @@ package org.dolphinemu.dolphinemu.features.settings.ui;
import androidx.annotation.NonNull;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+
public enum MenuTag
{
SETTINGS("settings"),
@@ -31,14 +33,26 @@ public enum MenuTag
GCPAD_2("gcpad", 1),
GCPAD_3("gcpad", 2),
GCPAD_4("gcpad", 3),
- WIIMOTE_1("wiimote", 4),
- WIIMOTE_2("wiimote", 5),
- WIIMOTE_3("wiimote", 6),
- WIIMOTE_4("wiimote", 7),
- WIIMOTE_EXTENSION_1("wiimote_extension", 4),
- WIIMOTE_EXTENSION_2("wiimote_extension", 5),
- WIIMOTE_EXTENSION_3("wiimote_extension", 6),
- WIIMOTE_EXTENSION_4("wiimote_extension", 7);
+ WIIMOTE_1("wiimote", 0),
+ WIIMOTE_2("wiimote", 1),
+ WIIMOTE_3("wiimote", 2),
+ WIIMOTE_4("wiimote", 3),
+ WIIMOTE_EXTENSION_1("wiimote_extension", 0),
+ WIIMOTE_EXTENSION_2("wiimote_extension", 1),
+ WIIMOTE_EXTENSION_3("wiimote_extension", 2),
+ WIIMOTE_EXTENSION_4("wiimote_extension", 3),
+ WIIMOTE_GENERAL_1("wiimote_general", 0),
+ WIIMOTE_GENERAL_2("wiimote_general", 1),
+ WIIMOTE_GENERAL_3("wiimote_general", 2),
+ WIIMOTE_GENERAL_4("wiimote_general", 3),
+ WIIMOTE_MOTION_SIMULATION_1("wiimote_motion_simulation", 0),
+ WIIMOTE_MOTION_SIMULATION_2("wiimote_motion_simulation", 1),
+ WIIMOTE_MOTION_SIMULATION_3("wiimote_motion_simulation", 2),
+ WIIMOTE_MOTION_SIMULATION_4("wiimote_motion_simulation", 3),
+ WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0),
+ WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1),
+ WIIMOTE_MOTION_INPUT_3("wiimote_motion_input", 2),
+ WIIMOTE_MOTION_INPUT_4("wiimote_motion_input", 3);
private String tag;
private int subType = -1;
@@ -76,6 +90,16 @@ public enum MenuTag
return subType;
}
+ public EmulatedController getCorrespondingEmulatedController()
+ {
+ if (isGCPadMenu())
+ return EmulatedController.getGcPad(getSubType());
+ else if (isWiimoteMenu())
+ return EmulatedController.getWiimote(getSubType());
+ else
+ throw new UnsupportedOperationException();
+ }
+
public boolean isSerialPort1Menu()
{
return this == CONFIG_SERIALPORT1;
@@ -112,11 +136,27 @@ public enum MenuTag
return getMenuTag("wiimote_extension", subtype);
}
+ public static MenuTag getWiimoteGeneralMenuTag(int subtype)
+ {
+ return getMenuTag("wiimote_general", subtype);
+ }
+
+ public static MenuTag getWiimoteMotionSimulationMenuTag(int subtype)
+ {
+ return getMenuTag("wiimote_motion_simulation", subtype);
+ }
+
+ public static MenuTag getWiimoteMotionInputMenuTag(int subtype)
+ {
+ return getMenuTag("wiimote_motion_input", subtype);
+ }
+
private static MenuTag getMenuTag(String tag, int subtype)
{
for (MenuTag menuTag : MenuTag.values())
{
- if (menuTag.tag.equals(tag) && menuTag.subType == subtype) return menuTag;
+ if (menuTag.tag.equals(tag) && menuTag.subType == subtype)
+ return menuTag;
}
throw new IllegalArgumentException("You are asking for a menu that is not available or " +
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java
index 2af17e2d2f..bce24c1c46 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java
@@ -9,6 +9,7 @@ import android.os.Bundle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -18,6 +19,7 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
+import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
@@ -41,15 +43,18 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
private static final String ARG_GAME_ID = "game_id";
private static final String ARG_REVISION = "revision";
private static final String ARG_IS_WII = "is_wii";
+ private static final String KEY_MAPPING_ALL_DEVICES = "all_devices";
private static final String FRAGMENT_TAG = "settings";
- private SettingsActivityPresenter mPresenter;
+ private static final String FRAGMENT_DIALOG_TAG = "settings_dialog";
+ private SettingsActivityPresenter mPresenter;
private AlertDialog dialog;
-
private CollapsingToolbarLayout mToolbarLayout;
private ActivitySettingsBinding mBinding;
+ private boolean mMappingAllDevices = false;
+
public static void launch(Context context, MenuTag menuTag, String gameId, int revision,
boolean isWii)
{
@@ -82,6 +87,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
{
MainPresenter.skipRescanningLibrary();
}
+ else
+ {
+ mMappingAllDevices = savedInstanceState.getBoolean(KEY_MAPPING_ALL_DEVICES);
+ }
mBinding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
@@ -125,7 +134,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
{
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState);
+
mPresenter.saveState(outState);
+
+ outState.putBoolean(KEY_MAPPING_ALL_DEVICES, mMappingAllDevices);
}
@Override
@@ -181,6 +193,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
transaction.commit();
}
+ @Override
+ public void showDialogFragment(DialogFragment fragment)
+ {
+ fragment.show(getSupportFragmentManager(), FRAGMENT_DIALOG_TAG);
+ }
+
private boolean areSystemAnimationsEnabled()
{
float duration = Settings.Global.getFloat(
@@ -305,6 +323,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
}
@Override
+ public void onControllerSettingsChanged()
+ {
+ getFragment().onControllerSettingsChanged();
+ }
+
+ @Override
public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
{
mPresenter.onMenuTagAction(menuTag, value);
@@ -330,7 +354,27 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
public void setToolbarTitle(String title)
{
- mToolbarLayout.setTitle(title);
+ mBinding.toolbarSettingsLayout.setTitle(title);
+ }
+
+ @Override
+ public void setMappingAllDevices(boolean allDevices)
+ {
+ mMappingAllDevices = allDevices;
+ }
+
+ @Override
+ public boolean isMappingAllDevices()
+ {
+ return mMappingAllDevices;
+ }
+
+ @Override
+ public int setOldControllerSettingsWarningVisibility(boolean visible)
+ {
+ // We use INVISIBLE instead of GONE to avoid getting a stale height for the return value
+ mBinding.oldControllerSettingsWarning.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ return visible ? mBinding.oldControllerSettingsWarning.getHeight() : 0;
}
private void setInsets()
@@ -343,6 +387,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
mBinding.frameContentSettings.setPadding(insets.left, 0, insets.right, 0);
+ int textPadding = getResources().getDimensionPixelSize(R.dimen.spacing_large);
+ mBinding.oldControllerSettingsWarning.setPadding(textPadding + insets.left, textPadding,
+ textPadding + insets.right, textPadding + insets.bottom);
+
InsetsHelper.applyNavbarWorkaround(insets.bottom, mBinding.workaroundView);
ThemeHelper.setNavigationBarColor(this,
MaterialColors.getColor(mBinding.appbarSettings, R.attr.colorSurface));
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java
index c835aa4399..464d0ea165 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java
@@ -5,6 +5,7 @@ package org.dolphinemu.dolphinemu.features.settings.ui;
import android.os.Bundle;
import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
@@ -22,6 +23,13 @@ public interface SettingsActivityView
void showSettingsFragment(MenuTag menuTag, Bundle extras, boolean addToStack, String gameId);
/**
+ * Shows a DialogFragment.
+ *
+ * Only one can be shown at a time.
+ */
+ void showDialogFragment(DialogFragment fragment);
+
+ /**
* Called by a contained Fragment to get access to the Setting HashMap
* loaded from disk, so that each Fragment doesn't need to perform its own
* read operation.
@@ -61,6 +69,14 @@ public interface SettingsActivityView
void onSettingChanged();
/**
+ * Refetches the values of all controller settings.
+ *
+ * To be used when loading an input profile or performing some other action that changes all
+ * controller settings at once.
+ */
+ void onControllerSettingsChanged();
+
+ /**
* Called by a containing Fragment to tell the containing Activity that the user wants to open the
* MenuTag associated with a setting.
*
@@ -97,4 +113,25 @@ public interface SettingsActivityView
* Accesses the material toolbar layout and changes the title
*/
void setToolbarTitle(String title);
+
+ /**
+ * Sets whether the input mapping dialog should detect inputs from all devices,
+ * not just the device configured for the controller.
+ */
+ void setMappingAllDevices(boolean allDevices);
+
+ /**
+ * Returns whether the input mapping dialog should detect inputs from all devices,
+ * not just the device configured for the controller.
+ */
+ boolean isMappingAllDevices();
+
+ /**
+ * Shows or hides a warning telling the user that they're using incompatible controller settings.
+ * The warning is hidden by default.
+ *
+ * @param visible Whether the warning should be visible.
+ * @return The height of the warning view, or 0 if the view is now invisible.
+ */
+ int setOldControllerSettingsWarningVisibility(boolean visible);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java
index a7f028afe3..ca405b15fd 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java
@@ -31,21 +31,24 @@ import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding;
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
import org.dolphinemu.dolphinemu.databinding.DialogSliderBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding;
+import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
-import org.dolphinemu.dolphinemu.dialogs.MotionAlertDialog;
+import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog;
+import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog;
+import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
+import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker;
import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions;
@@ -57,9 +60,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.DateTimeSetting
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.FilePickerViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderHyperLinkViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputBindingSettingViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputStringSettingViewHolder;
-import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RumbleBindingViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RunRunnableViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder;
@@ -124,13 +125,9 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
case SettingsItem.TYPE_SUBMENU:
return new SubmenuViewHolder(ListItemSubmenuBinding.inflate(inflater), this);
- case SettingsItem.TYPE_INPUT_BINDING:
- return new InputBindingSettingViewHolder(ListItemSettingBinding.inflate(inflater), this,
- mContext);
-
- case SettingsItem.TYPE_RUMBLE_BINDING:
- return new RumbleBindingViewHolder(ListItemSettingBinding.inflate(inflater), this,
- mContext);
+ case SettingsItem.TYPE_INPUT_MAPPING_CONTROL:
+ return new InputMappingControlSettingViewHolder(ListItemMappingBinding.inflate(inflater),
+ this);
case SettingsItem.TYPE_FILE_PICKER:
return new FilePickerViewHolder(ListItemSettingBinding.inflate(inflater), this);
@@ -259,11 +256,26 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
mClickedItem = item;
mClickedPosition = position;
- mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
- .setTitle(item.getName())
- .setSingleChoiceItems(item.getChoices(), item.getSelectedValueIndex(getSettings()),
- this)
- .show();
+ item.refreshChoicesAndValues();
+
+ String[] choices = item.getChoices();
+ int noChoicesAvailableString = item.getNoChoicesAvailableString();
+ if (noChoicesAvailableString != 0 && choices.length == 0)
+ {
+ mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
+ .setTitle(item.getName())
+ .setMessage(noChoicesAvailableString)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ }
+ else
+ {
+ mDialog = new MaterialAlertDialogBuilder(mView.getActivity())
+ .setTitle(item.getName())
+ .setSingleChoiceItems(item.getChoices(), item.getSelectedValueIndex(getSettings()),
+ this)
+ .show();
+ }
}
public void onSingleChoiceDynamicDescriptionsClick(SingleChoiceSettingDynamicDescriptions item,
@@ -314,9 +326,19 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
mView.loadSubMenu(item.getMenuKey());
}
- public void onInputBindingClick(final InputBindingSetting item, final int position)
+ public void onInputMappingClick(final InputMappingControlSetting item, final int position)
{
- final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item, this);
+ if (item.getController().getDefaultDevice().isEmpty() && !mView.isMappingAllDevices())
+ {
+ new MaterialAlertDialogBuilder(mView.getActivity())
+ .setMessage(R.string.input_binding_no_device)
+ .setPositiveButton(R.string.ok, this)
+ .show();
+ return;
+ }
+
+ final MotionAlertDialog dialog = new MotionAlertDialog(mView.getActivity(), item,
+ mView.isMappingAllDevices());
Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
@ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
@@ -326,13 +348,11 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
dialog.getWindow().setBackgroundDrawable(background);
dialog.setTitle(R.string.input_binding);
- dialog.setMessage(String.format(mContext.getString(
- item instanceof RumbleBindingSetting ?
- R.string.input_rumble_description : R.string.input_binding_description),
+ dialog.setMessage(String.format(mContext.getString(R.string.input_binding_description),
item.getName()));
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
- (dialogInterface, i) -> item.clearValue(getSettings()));
+ (dialogInterface, i) -> item.clearValue());
dialog.setOnDismissListener(dialog1 ->
{
notifyItemChanged(position);
@@ -342,6 +362,44 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
dialog.show();
}
+ public void onAdvancedInputMappingClick(final InputMappingControlSetting item, final int position)
+ {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ DialogAdvancedMappingBinding binding = DialogAdvancedMappingBinding.inflate(inflater);
+
+ final AdvancedMappingDialog dialog = new AdvancedMappingDialog(mContext, binding,
+ item.getControlReference(), item.getController());
+
+ Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
+ @ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
+ MaterialColors.getColor(dialog.getWindow().getDecorView(), R.attr.colorSurface),
+ dialog.getWindow().getDecorView().getElevation());
+ background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ dialog.getWindow().setBackgroundDrawable(background);
+
+ dialog.setTitle(item.isInput() ?
+ R.string.input_configure_input : R.string.input_configure_output);
+ dialog.setView(binding.getRoot());
+ dialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.ok),
+ (dialogInterface, i) ->
+ {
+ item.setValue(dialog.getExpression());
+ notifyItemChanged(position);
+ mView.onSettingChanged();
+ });
+ dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
+ dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
+ (dialogInterface, i) ->
+ {
+ item.clearValue();
+ notifyItemChanged(position);
+ mView.onSettingChanged();
+ });
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.show();
+ }
+
public void onFilePickerDirectoryClick(SettingsItem item, int position)
{
mClickedItem = item;
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java
index 81c7321339..1a7905565d 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java
@@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -36,6 +37,8 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
private SettingsAdapter mAdapter;
+ private int mOldControllerSettingsWarningHeight = 0;
+
private static final Map<MenuTag, Integer> titles = new HashMap<>();
static
@@ -65,14 +68,26 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
titles.put(MenuTag.GCPAD_2, R.string.controller_1);
titles.put(MenuTag.GCPAD_3, R.string.controller_2);
titles.put(MenuTag.GCPAD_4, R.string.controller_3);
- titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_4);
- titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_5);
- titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_6);
- titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_7);
- titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_4);
- titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_5);
- titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_6);
- titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_7);
+ titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_0);
+ titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_1);
+ titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_2);
+ titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_3);
+ titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_0);
+ titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_1);
+ titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_2);
+ titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_3);
+ titles.put(MenuTag.WIIMOTE_GENERAL_1, R.string.wiimote_general);
+ titles.put(MenuTag.WIIMOTE_GENERAL_2, R.string.wiimote_general);
+ titles.put(MenuTag.WIIMOTE_GENERAL_3, R.string.wiimote_general);
+ titles.put(MenuTag.WIIMOTE_GENERAL_4, R.string.wiimote_general);
+ titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_1, R.string.wiimote_motion_simulation);
+ titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_2, R.string.wiimote_motion_simulation);
+ titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_3, R.string.wiimote_motion_simulation);
+ titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_4, R.string.wiimote_motion_simulation);
+ titles.put(MenuTag.WIIMOTE_MOTION_INPUT_1, R.string.wiimote_motion_input);
+ titles.put(MenuTag.WIIMOTE_MOTION_INPUT_2, R.string.wiimote_motion_input);
+ titles.put(MenuTag.WIIMOTE_MOTION_INPUT_3, R.string.wiimote_motion_input);
+ titles.put(MenuTag.WIIMOTE_MOTION_INPUT_4, R.string.wiimote_motion_input);
}
private FragmentSettingsBinding mBinding;
@@ -204,6 +219,12 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
}
@Override
+ public void showDialogFragment(DialogFragment fragment)
+ {
+ mActivity.showDialogFragment(fragment);
+ }
+
+ @Override
public void showToastMessage(String message)
{
mActivity.showToastMessage(message);
@@ -222,6 +243,13 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
}
@Override
+ public void onControllerSettingsChanged()
+ {
+ mAdapter.notifyAllSettingsChanged();
+ mPresenter.updateOldControllerSettingsWarningVisibility();
+ }
+
+ @Override
public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
{
mActivity.onMenuTagAction(menuTag, value);
@@ -232,13 +260,35 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
return mActivity.hasMenuTagActionForValue(menuTag, value);
}
+ @Override
+ public void setMappingAllDevices(boolean allDevices)
+ {
+ mActivity.setMappingAllDevices(allDevices);
+ }
+
+ @Override
+ public boolean isMappingAllDevices()
+ {
+ return mActivity.isMappingAllDevices();
+ }
+
+ @Override
+ public void setOldControllerSettingsWarningVisibility(boolean visible)
+ {
+ mOldControllerSettingsWarningHeight =
+ mActivity.setOldControllerSettingsWarningVisibility(visible);
+
+ // Trigger the insets listener we've registered
+ mBinding.listSettings.requestApplyInsets();
+ }
+
private void setInsets()
{
ViewCompat.setOnApplyWindowInsetsListener(mBinding.listSettings, (v, windowInsets) ->
{
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
- v.setPadding(0, 0, 0,
- insets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_list));
+ int listSpacing = getResources().getDimensionPixelSize(R.dimen.spacing_list);
+ v.setPadding(0, 0, 0, insets.bottom + listSpacing + mOldControllerSettingsWarningHeight);
return windowInsets;
});
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
index 3604d53087..cd39f8c10f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java
@@ -10,13 +10,24 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.collection.ArraySet;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.UserDataActivity;
+import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting;
+import org.dolphinemu.dolphinemu.features.input.model.InputMappingDoubleSetting;
+import org.dolphinemu.dolphinemu.features.input.model.InputMappingIntSetting;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup;
+import org.dolphinemu.dolphinemu.features.input.model.ControlGroupEnabledSetting;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
+import org.dolphinemu.dolphinemu.features.input.model.view.InputDeviceSetting;
+import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
+import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog;
+import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialogPresenter;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.AbstractStringSetting;
import org.dolphinemu.dolphinemu.features.settings.model.AdHocBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting;
@@ -26,26 +37,23 @@ import org.dolphinemu.dolphinemu.features.settings.model.PostProcessing;
import org.dolphinemu.dolphinemu.features.settings.model.ScaledIntSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.WiimoteProfileStringSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker;
+import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.HeaderSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.HyperLinkHeaderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.InputStringSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.InvertedSwitchSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.LogSwitchSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.PercentSliderSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.RunRunnable;
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions;
import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting;
-import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
import org.dolphinemu.dolphinemu.utils.BooleanSupplier;
import org.dolphinemu.dolphinemu.utils.EGLHelper;
@@ -54,8 +62,11 @@ import org.dolphinemu.dolphinemu.utils.ThreadUtil;
import org.dolphinemu.dolphinemu.utils.WiiUtils;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Set;
public final class SettingsFragmentPresenter
{
@@ -72,6 +83,7 @@ public final class SettingsFragmentPresenter
private Settings mSettings;
private ArrayList<SettingsItem> mSettingsList;
+ private boolean mHasOldControllerSettings = false;
private int mSerialPort1Type;
private int mControllerNumber;
@@ -136,6 +148,7 @@ public final class SettingsFragmentPresenter
else
{
mView.showSettingsList(mSettingsList);
+ mView.setOldControllerSettingsWarningVisibility(mHasOldControllerSettings);
}
}
@@ -246,6 +259,27 @@ public final class SettingsFragmentPresenter
addExtensionTypeSettings(sl, mControllerNumber, mControllerType);
break;
+ case WIIMOTE_GENERAL_1:
+ case WIIMOTE_GENERAL_2:
+ case WIIMOTE_GENERAL_3:
+ case WIIMOTE_GENERAL_4:
+ addWiimoteGeneralSubSettings(sl, mControllerNumber);
+ break;
+
+ case WIIMOTE_MOTION_SIMULATION_1:
+ case WIIMOTE_MOTION_SIMULATION_2:
+ case WIIMOTE_MOTION_SIMULATION_3:
+ case WIIMOTE_MOTION_SIMULATION_4:
+ addWiimoteMotionSimulationSubSettings(sl, mControllerNumber);
+ break;
+
+ case WIIMOTE_MOTION_INPUT_1:
+ case WIIMOTE_MOTION_INPUT_2:
+ case WIIMOTE_MOTION_INPUT_3:
+ case WIIMOTE_MOTION_INPUT_4:
+ addWiimoteMotionInputSubSettings(sl, mControllerNumber);
+ break;
+
default:
throw new UnsupportedOperationException("Unimplemented menu");
}
@@ -259,13 +293,10 @@ public final class SettingsFragmentPresenter
sl.add(new SubmenuSetting(mContext, R.string.config, MenuTag.CONFIG));
sl.add(new SubmenuSetting(mContext, R.string.graphics_settings, MenuTag.GRAPHICS));
- if (!NativeLibrary.IsRunning())
+ sl.add(new SubmenuSetting(mContext, R.string.gcpad_settings, MenuTag.GCPAD_TYPE));
+ if (mSettings.isWii())
{
- sl.add(new SubmenuSetting(mContext, R.string.gcpad_settings, MenuTag.GCPAD_TYPE));
- if (mSettings.isWii())
- {
- sl.add(new SubmenuSetting(mContext, R.string.wiimote_settings, MenuTag.WIIMOTE));
- }
+ sl.add(new SubmenuSetting(mContext, R.string.wiimote_settings, MenuTag.WIIMOTE));
}
sl.add(new HeaderSetting(mContext, R.string.setting_clear_info, 0));
@@ -801,14 +832,14 @@ public final class SettingsFragmentPresenter
private void addWiimoteSettings(ArrayList<SettingsItem> sl)
{
- sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_4, 0,
- R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(4)));
- sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_5, 0,
- R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(5)));
- sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_6, 0,
- R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(6)));
- sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_7, 0,
- R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(7)));
+ sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_0, 0,
+ R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(0)));
+ sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_1, 0,
+ R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(1)));
+ sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_2, 0,
+ R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(2)));
+ sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_3, 0,
+ R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(3)));
}
private void addGraphicsSettings(ArrayList<SettingsItem> sl)
@@ -1079,62 +1110,17 @@ public final class SettingsFragmentPresenter
{
if (gcPadType == 6) // Emulated
{
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_A + gcPadNumber, R.string.button_a, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_B + gcPadNumber, R.string.button_b, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_X + gcPadNumber, R.string.button_x, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_Y + gcPadNumber, R.string.button_y, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_Z + gcPadNumber, R.string.button_z, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_START + gcPadNumber, R.string.button_start, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_control, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_CONTROL_UP + gcPadNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_CONTROL_DOWN + gcPadNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_CONTROL_LEFT + gcPadNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_CONTROL_RIGHT + gcPadNumber, R.string.generic_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_c, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_C_UP + gcPadNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_C_DOWN + gcPadNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_C_LEFT + gcPadNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_C_RIGHT + gcPadNumber, R.string.generic_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_trig, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_TRIGGER_L + gcPadNumber, R.string.trigger_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_TRIGGER_R + gcPadNumber, R.string.trigger_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_DPAD_UP + gcPadNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_DPAD_DOWN + gcPadNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_DPAD_LEFT + gcPadNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber, R.string.generic_right, mGameID));
-
-
- sl.add(new HeaderSetting(mContext, R.string.emulation_control_rumble, 0));
- sl.add(new RumbleBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_EMU_RUMBLE + gcPadNumber, R.string.emulation_control_rumble,
- mGameID));
+ EmulatedController gcPad = EmulatedController.getGcPad(gcPadNumber);
+
+ if (!TextUtils.isEmpty(mGameID))
+ {
+ addControllerPerGameSettings(sl, "Pad", gcPadNumber);
+ }
+ else
+ {
+ addControllerMetaSettings(sl, gcPad);
+ addControllerMappingSettings(sl, gcPad, null);
+ }
}
else if (gcPadType == 12) // Adapter
{
@@ -1147,440 +1133,227 @@ public final class SettingsFragmentPresenter
private void addWiimoteSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{
- // Bindings use controller numbers 4-7 (0-3 are GameCube), but the extension setting uses 1-4.
- // But game specific extension settings are saved in their own profile. These profiles
- // do not have any way to specify the controller that is loaded outside of knowing the filename
- // of the profile that was loaded.
- AbstractStringSetting extension;
- final String defaultExtension = "None";
- if (mGameID.isEmpty())
+ EmulatedController wiimote = EmulatedController.getWiimote(wiimoteNumber);
+
+ if (!TextUtils.isEmpty(mGameID))
{
- extension = new LegacyStringSetting(Settings.FILE_WIIMOTE,
- Settings.SECTION_WIIMOTE + (wiimoteNumber - 3), SettingsFile.KEY_WIIMOTE_EXTENSION,
- defaultExtension);
+ addControllerPerGameSettings(sl, "Wiimote", wiimoteNumber);
}
else
{
- extension = new WiimoteProfileStringSetting(mGameID, wiimoteNumber - 4,
- Settings.SECTION_PROFILE, SettingsFile.KEY_WIIMOTE_EXTENSION, defaultExtension);
+ addControllerMetaSettings(sl, wiimote);
+
+ sl.add(new HeaderSetting(mContext, R.string.wiimote, 0));
+ sl.add(new SubmenuSetting(mContext, R.string.wiimote_general,
+ MenuTag.getWiimoteGeneralMenuTag(wiimoteNumber)));
+ sl.add(new SubmenuSetting(mContext, R.string.wiimote_motion_simulation,
+ MenuTag.getWiimoteMotionSimulationMenuTag(wiimoteNumber)));
+ sl.add(new SubmenuSetting(mContext, R.string.wiimote_motion_input,
+ MenuTag.getWiimoteMotionInputMenuTag(wiimoteNumber)));
+
+ // TYPE_OTHER is included here instead of in addWiimoteGeneralSubSettings so that touchscreen
+ // users won't have to dig into a submenu to find the Sideways Wii Remote setting
+ addControllerMappingSettings(sl, wiimote, new ArraySet<>(
+ Arrays.asList(ControlGroup.TYPE_ATTACHMENTS, ControlGroup.TYPE_OTHER)));
}
-
- sl.add(new StringSingleChoiceSetting(mContext, extension, R.string.wiimote_extensions, 0,
- R.array.wiimoteExtensionsEntries, R.array.wiimoteExtensionsValues,
- MenuTag.getWiimoteExtensionMenuTag(wiimoteNumber)));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_A + wiimoteNumber, R.string.button_a, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_B + wiimoteNumber, R.string.button_b, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_1 + wiimoteNumber, R.string.button_one, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_2 + wiimoteNumber, R.string.button_two, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_MINUS + wiimoteNumber, R.string.button_minus, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_PLUS + wiimoteNumber, R.string.button_plus, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_HOME + wiimoteNumber, R.string.button_home, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_ir, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_UP + wiimoteNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_DOWN + wiimoteNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_LEFT + wiimoteNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_RIGHT + wiimoteNumber, R.string.generic_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_FORWARD + wiimoteNumber, R.string.generic_forward,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_BACKWARD + wiimoteNumber, R.string.generic_backward,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_IR_HIDE + wiimoteNumber, R.string.ir_hide, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_swing, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_UP + wiimoteNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_DOWN + wiimoteNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_LEFT + wiimoteNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_RIGHT + wiimoteNumber, R.string.generic_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_FORWARD + wiimoteNumber, R.string.generic_forward,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SWING_BACKWARD + wiimoteNumber, R.string.generic_backward,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_tilt, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TILT_FORWARD + wiimoteNumber, R.string.generic_forward,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TILT_BACKWARD + wiimoteNumber, R.string.generic_backward,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TILT_LEFT + wiimoteNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TILT_RIGHT + wiimoteNumber, R.string.generic_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TILT_MODIFIER + wiimoteNumber, R.string.tilt_modifier,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_shake, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SHAKE_X + wiimoteNumber, R.string.shake_x, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SHAKE_Y + wiimoteNumber, R.string.shake_y, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_SHAKE_Z + wiimoteNumber, R.string.shake_z, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DPAD_UP + wiimoteNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DPAD_DOWN + wiimoteNumber, R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DPAD_LEFT + wiimoteNumber, R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber, R.string.generic_right, mGameID));
-
-
- sl.add(new HeaderSetting(mContext, R.string.emulation_control_rumble, 0));
- sl.add(new RumbleBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_EMU_RUMBLE + wiimoteNumber, R.string.emulation_control_rumble,
- mGameID));
}
private void addExtensionTypeSettings(ArrayList<SettingsItem> sl, int wiimoteNumber,
- int extentionType)
+ int extensionType)
+ {
+ addControllerMappingSettings(sl,
+ EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType), null);
+ }
+
+ private void addWiimoteGeneralSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
+ {
+ addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
+ Collections.singleton(ControlGroup.TYPE_BUTTONS));
+ }
+
+ private void addWiimoteMotionSimulationSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
+ {
+ addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
+ new ArraySet<>(Arrays.asList(ControlGroup.TYPE_FORCE, ControlGroup.TYPE_TILT,
+ ControlGroup.TYPE_CURSOR, ControlGroup.TYPE_SHAKE)));
+ }
+
+ private void addWiimoteMotionInputSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
{
- switch (extentionType)
+ addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber),
+ new ArraySet<>(Arrays.asList(ControlGroup.TYPE_IMU_ACCELEROMETER,
+ ControlGroup.TYPE_IMU_GYROSCOPE, ControlGroup.TYPE_IMU_CURSOR)));
+ }
+
+ /**
+ * Adds controller settings that can be set on a per-game basis.
+ *
+ * @param sl The list to place controller settings into.
+ * @param profileString The prefix used for the profile setting in game INI files.
+ * @param controllerNumber The index of the controller, 0-3.
+ */
+ private void addControllerPerGameSettings(ArrayList<SettingsItem> sl, String profileString,
+ int controllerNumber)
+ {
+ String[] profiles = new ProfileDialogPresenter(mMenuTag).getProfileNames(false);
+ String profileKey = profileString + "Profile" + controllerNumber;
+ sl.add(new StringSingleChoiceSetting(mContext,
+ new LegacyStringSetting("", "Controls", profileKey, ""),
+ R.string.input_profile, 0, profiles, profiles, R.string.input_profiles_empty));
+ }
+
+ /**
+ * Adds settings and actions that apply to a controller as a whole.
+ * For instance, the device setting and the Clear action.
+ *
+ * @param sl The list to place controller settings into.
+ * @param controller The controller to add settings for.
+ */
+ private void addControllerMetaSettings(ArrayList<SettingsItem> sl, EmulatedController controller)
+ {
+ sl.add(new InputDeviceSetting(mContext, R.string.input_device, 0, controller));
+
+ sl.add(new SwitchSetting(mContext, new AbstractBooleanSetting()
{
- case 1: // Nunchuk
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_C + wiimoteNumber, R.string.nunchuk_button_c,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_Z + wiimoteNumber, R.string.button_z, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_UP + wiimoteNumber, R.string.generic_up, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_swing, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_FORWARD + wiimoteNumber,
- R.string.generic_forward, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_BACKWARD + wiimoteNumber,
- R.string.generic_backward, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_tilt, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_FORWARD + wiimoteNumber,
- R.string.generic_forward, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_BACKWARD + wiimoteNumber,
- R.string.generic_backward, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_MODIFIER + wiimoteNumber,
- R.string.tilt_modifier, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.wiimote_shake, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_X + wiimoteNumber, R.string.shake_x,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_Y + wiimoteNumber, R.string.shake_y,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_Z + wiimoteNumber, R.string.shake_z,
- mGameID));
- break;
- case 2: // Classic
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_A + wiimoteNumber, R.string.button_a, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_B + wiimoteNumber, R.string.button_b, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_X + wiimoteNumber, R.string.button_x, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_Y + wiimoteNumber, R.string.button_y, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_ZL + wiimoteNumber, R.string.classic_button_zl,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_ZR + wiimoteNumber, R.string.classic_button_zr,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_MINUS + wiimoteNumber, R.string.button_minus,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_PLUS + wiimoteNumber, R.string.button_plus,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_HOME + wiimoteNumber, R.string.button_home,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.classic_leftstick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.classic_rightstick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_trig, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_TRIGGER_L + wiimoteNumber, R.string.trigger_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_TRIGGER_R + wiimoteNumber, R.string.trigger_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
- break;
- case 3: // Guitar
- sl.add(new HeaderSetting(mContext, R.string.guitar_frets, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_FRET_GREEN + wiimoteNumber, R.string.generic_green,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_FRET_RED + wiimoteNumber, R.string.generic_red,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_FRET_YELLOW + wiimoteNumber,
- R.string.generic_yellow, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_FRET_BLUE + wiimoteNumber, R.string.generic_blue,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_FRET_ORANGE + wiimoteNumber,
- R.string.generic_orange, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.guitar_strum, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STRUM_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STRUM_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_MINUS + wiimoteNumber, R.string.button_minus,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_PLUS + wiimoteNumber, R.string.button_plus,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STICK_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STICK_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STICK_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_STICK_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.guitar_whammy, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_GUITAR_WHAMMY_BAR + wiimoteNumber, R.string.generic_right,
- mGameID));
- break;
- case 4: // Drums
- sl.add(new HeaderSetting(mContext, R.string.drums_pads, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_RED + wiimoteNumber, R.string.generic_red,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_YELLOW + wiimoteNumber, R.string.generic_yellow,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_BLUE + wiimoteNumber, R.string.generic_blue,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_GREEN + wiimoteNumber, R.string.generic_green,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_ORANGE + wiimoteNumber, R.string.generic_orange,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PAD_BASS + wiimoteNumber, R.string.drums_pad_bass,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_STICK_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_STICK_DOWN + wiimoteNumber, R.string.generic_down,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_STICK_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_STICK_RIGHT + wiimoteNumber, R.string.generic_right,
- mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_MINUS + wiimoteNumber, R.string.button_minus,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_DRUMS_PLUS + wiimoteNumber, R.string.button_plus,
- mGameID));
+ @Override
+ public boolean isOverridden(Settings settings)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isRuntimeEditable()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean delete(Settings settings)
+ {
+ mView.setMappingAllDevices(false);
+ return true;
+ }
+
+ @Override
+ public boolean getBoolean(Settings settings)
+ {
+ return mView.isMappingAllDevices();
+ }
+
+ @Override
+ public void setBoolean(Settings settings, boolean newValue)
+ {
+ mView.setMappingAllDevices(newValue);
+ }
+ }, R.string.input_device_all_devices, R.string.input_device_all_devices_description));
+
+ sl.add(new RunRunnable(mContext, R.string.input_reset_to_default,
+ R.string.input_reset_to_default_description, R.string.input_reset_warning, 0, true,
+ () -> loadDefaultControllerSettings(controller)));
+
+ sl.add(new RunRunnable(mContext, R.string.input_clear, R.string.input_clear_description,
+ R.string.input_reset_warning, 0, true, () -> clearControllerSettings(controller)));
+
+ sl.add(new RunRunnable(mContext, R.string.input_profiles, 0, 0, 0, true,
+ () -> mView.showDialogFragment(ProfileDialog.create(mMenuTag))));
+
+ updateOldControllerSettingsWarningVisibility(controller);
+ }
+
+ /**
+ * Adds mapping settings and other control-specific settings.
+ *
+ * @param sl The list to place controller settings into.
+ * @param controller The controller to add settings for.
+ * @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
+ */
+ private void addControllerMappingSettings(ArrayList<SettingsItem> sl,
+ EmulatedController controller, Set<Integer> groupTypeFilter)
+ {
+ updateOldControllerSettingsWarningVisibility(controller);
+
+ int groupCount = controller.getGroupCount();
+ for (int i = 0; i < groupCount; i++)
+ {
+ ControlGroup group = controller.getGroup(i);
+ int groupType = group.getGroupType();
+ if (groupTypeFilter != null && !groupTypeFilter.contains(groupType))
+ continue;
+
+ sl.add(new HeaderSetting(group.getUiName(), ""));
+
+ if (group.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_ALWAYS)
+ {
+ sl.add(new SwitchSetting(mContext, new ControlGroupEnabledSetting(group), R.string.enabled,
+ 0));
+ }
+
+ int controlCount = group.getControlCount();
+ for (int j = 0; j < controlCount; j++)
+ {
+ sl.add(new InputMappingControlSetting(group.getControl(j), controller));
+ }
+
+ if (groupType == ControlGroup.TYPE_ATTACHMENTS)
+ {
+ NumericSetting attachmentSetting = group.getAttachmentSetting();
+ sl.add(new SingleChoiceSetting(mContext, new InputMappingIntSetting(attachmentSetting),
+ R.string.wiimote_extensions, 0, R.array.wiimoteExtensionsEntries,
+ R.array.wiimoteExtensionsValues,
+ MenuTag.getWiimoteExtensionMenuTag(mControllerNumber)));
+ }
+
+ int numericSettingCount = group.getNumericSettingCount();
+ for (int j = 0; j < numericSettingCount; j++)
+ {
+ addNumericSetting(sl, group.getNumericSetting(j));
+ }
+ }
+ }
+
+ private void addNumericSetting(ArrayList<SettingsItem> sl, NumericSetting setting)
+ {
+ switch (setting.getType())
+ {
+ case NumericSetting.TYPE_DOUBLE:
+ sl.add(new FloatSliderSetting(new InputMappingDoubleSetting(setting), setting.getUiName(),
+ "", (int) Math.ceil(setting.getDoubleMin()),
+ (int) Math.floor(setting.getDoubleMax()), setting.getUiSuffix()));
break;
- case 5: // Turntable
- sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_GREEN_LEFT + wiimoteNumber,
- R.string.turntable_button_green_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_RED_LEFT + wiimoteNumber,
- R.string.turntable_button_red_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_BLUE_LEFT + wiimoteNumber,
- R.string.turntable_button_blue_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_GREEN_RIGHT + wiimoteNumber,
- R.string.turntable_button_green_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_RED_RIGHT + wiimoteNumber,
- R.string.turntable_button_red_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_BLUE_RIGHT + wiimoteNumber,
- R.string.turntable_button_blue_right, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_MINUS + wiimoteNumber,
- R.string.button_minus, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_PLUS + wiimoteNumber,
- R.string.button_plus, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_EUPHORIA + wiimoteNumber,
- R.string.turntable_button_euphoria, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.turntable_table_left, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_LEFT_LEFT + wiimoteNumber, R.string.generic_left,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_LEFT_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.turntable_table_right, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_RIGHT_LEFT + wiimoteNumber,
- R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_RIGHT_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_UP + wiimoteNumber, R.string.generic_up,
- mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_DOWN + wiimoteNumber,
- R.string.generic_down, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_LEFT + wiimoteNumber,
- R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.turntable_effect, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_EFFECT_DIAL + wiimoteNumber,
- R.string.turntable_effect_dial, mGameID));
-
- sl.add(new HeaderSetting(mContext, R.string.turntable_crossfade, 0));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_CROSSFADE_LEFT + wiimoteNumber,
- R.string.generic_left, mGameID));
- sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS,
- SettingsFile.KEY_WIIBIND_TURNTABLE_CROSSFADE_RIGHT + wiimoteNumber,
- R.string.generic_right, mGameID));
+ case NumericSetting.TYPE_BOOLEAN:
+ sl.add(new SwitchSetting(new InputMappingBooleanSetting(setting), setting.getUiName(),
+ setting.getUiDescription()));
break;
}
}
+ public void updateOldControllerSettingsWarningVisibility()
+ {
+ updateOldControllerSettingsWarningVisibility(mMenuTag.getCorrespondingEmulatedController());
+ }
+
+ private void updateOldControllerSettingsWarningVisibility(EmulatedController controller)
+ {
+ String defaultDevice = controller.getDefaultDevice();
+
+ mHasOldControllerSettings = defaultDevice.startsWith("Android/") &&
+ defaultDevice.endsWith("/Touchscreen");
+
+ mView.setOldControllerSettingsWarningVisibility(mHasOldControllerSettings);
+ }
+
+ private void loadDefaultControllerSettings(EmulatedController controller)
+ {
+ controller.loadDefaultSettings();
+ mView.onControllerSettingsChanged();
+ }
+
+ private void clearControllerSettings(EmulatedController controller)
+ {
+ controller.clearSettings();
+ mView.onControllerSettingsChanged();
+ }
+
private static int getLogVerbosityEntries()
{
// Value obtained from LogLevel in Common/Logging/Log.h
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java
index 295ff95abd..809f26f7cc 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java
@@ -3,6 +3,7 @@
package org.dolphinemu.dolphinemu.features.settings.ui;
import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
@@ -55,6 +56,8 @@ public interface SettingsFragmentView
*/
void loadSubMenu(MenuTag menuKey);
+ void showDialogFragment(DialogFragment fragment);
+
/**
* Tell the Fragment to tell the containing activity to display a toast message.
*
@@ -73,6 +76,14 @@ public interface SettingsFragmentView
void onSettingChanged();
/**
+ * Refetches the values of all controller settings.
+ *
+ * To be used when loading an input profile or performing some other action that changes all
+ * controller settings at once.
+ */
+ void onControllerSettingsChanged();
+
+ /**
* Have the fragment tell the containing Activity that the user wants to open the MenuTag
* associated with a setting.
*
@@ -89,4 +100,24 @@ public interface SettingsFragmentView
* @param value The current value of the setting.
*/
boolean hasMenuTagActionForValue(@NonNull MenuTag menuTag, int value);
+
+ /**
+ * Sets whether the input mapping dialog should detect inputs from all devices,
+ * not just the device configured for the controller.
+ */
+ void setMappingAllDevices(boolean allDevices);
+
+ /**
+ * Returns whether the input mapping dialog should detect inputs from all devices,
+ * not just the device configured for the controller.
+ */
+ boolean isMappingAllDevices();
+
+ /**
+ * Shows or hides a warning telling the user that they're using incompatible controller settings.
+ * The warning is hidden by default.
+ *
+ * @param visible Whether the warning should be visible.
+ */
+ void setOldControllerSettingsWarningVisibility(boolean visible);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java
deleted file mode 100644
index b61fcc458b..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.ui.viewholder;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceManager;
-
-import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
-import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
-import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
-
-public final class InputBindingSettingViewHolder extends SettingViewHolder
-{
- private InputBindingSetting mItem;
-
- private final Context mContext;
-
- private final ListItemSettingBinding mBinding;
-
- public InputBindingSettingViewHolder(@NonNull ListItemSettingBinding binding,
- SettingsAdapter adapter, Context context)
- {
- super(binding.getRoot(), adapter);
- mBinding = binding;
- mContext = context;
- }
-
- @Override
- public void bind(SettingsItem item)
- {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
-
- mItem = (InputBindingSetting) item;
-
- mBinding.textSettingName.setText(mItem.getName());
- mBinding.textSettingDescription
- .setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), ""));
-
- setStyle(mBinding.textSettingName, mItem);
- }
-
- @Override
- public void onClick(View clicked)
- {
- if (!mItem.isEditable())
- {
- showNotRuntimeEditableError();
- return;
- }
-
- getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition());
-
- setStyle(mBinding.textSettingName, mItem);
- }
-
- @Nullable @Override
- protected SettingsItem getItem()
- {
- return mItem;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java
deleted file mode 100644
index 3b7a6b0f75..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.features.settings.ui.viewholder;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceManager;
-
-import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
-import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
-import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
-
-public class RumbleBindingViewHolder extends SettingViewHolder
-{
- private RumbleBindingSetting mItem;
-
- private final Context mContext;
-
- private final ListItemSettingBinding mBinding;
-
- public RumbleBindingViewHolder(@NonNull ListItemSettingBinding binding, SettingsAdapter adapter,
- Context context)
- {
- super(binding.getRoot(), adapter);
- mBinding = binding;
- mContext = context;
- }
-
- @Override
- public void bind(SettingsItem item)
- {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
-
- mItem = (RumbleBindingSetting) item;
-
- mBinding.textSettingName.setText(item.getName());
- mBinding.textSettingDescription
- .setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), ""));
-
- setStyle(mBinding.textSettingName, mItem);
- }
-
- @Override
- public void onClick(View clicked)
- {
- if (!mItem.isEditable())
- {
- showNotRuntimeEditableError();
- return;
- }
-
- getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition());
-
- setStyle(mBinding.textSettingName, mItem);
- }
-
- @Nullable @Override
- protected SettingsItem getItem()
- {
- return mItem;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java
index d34e4a1536..18fd466461 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java
@@ -83,7 +83,7 @@ public abstract class SettingViewHolder extends RecyclerView.ViewHolder
{
SettingsItem item = getItem();
- if (item == null || !item.hasSetting())
+ if (item == null || !item.canClear())
return false;
if (!item.isEditable())
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java
index db56ebea40..beb15867c5 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java
@@ -64,10 +64,8 @@ public final class SingleChoiceViewHolder extends SettingViewHolder
else if (item instanceof StringSingleChoiceSetting)
{
StringSingleChoiceSetting setting = (StringSingleChoiceSetting) item;
- String[] choices = setting.getChoices();
- int valueIndex = setting.getSelectedValueIndex(settings);
- if (valueIndex != -1)
- mBinding.textSettingDescription.setText(choices[valueIndex]);
+ String choice = setting.getSelectedChoice(settings);
+ mBinding.textSettingDescription.setText(choice);
}
else if (item instanceof SingleChoiceSettingDynamicDescriptions)
{
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java
index 87f58b4d02..acad8c2d19 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java
@@ -20,161 +20,6 @@ public final class SettingsFile
public static final String KEY_ISO_PATH_BASE = "ISOPath";
public static final String KEY_ISO_PATHS = "ISOPaths";
- public static final String KEY_GCPAD_PLAYER_1 = "SIDevice0";
-
- public static final String KEY_GCBIND_A = "InputA_";
- public static final String KEY_GCBIND_B = "InputB_";
- public static final String KEY_GCBIND_X = "InputX_";
- public static final String KEY_GCBIND_Y = "InputY_";
- public static final String KEY_GCBIND_Z = "InputZ_";
- public static final String KEY_GCBIND_START = "InputStart_";
- public static final String KEY_GCBIND_CONTROL_UP = "MainUp_";
- public static final String KEY_GCBIND_CONTROL_DOWN = "MainDown_";
- public static final String KEY_GCBIND_CONTROL_LEFT = "MainLeft_";
- public static final String KEY_GCBIND_CONTROL_RIGHT = "MainRight_";
- public static final String KEY_GCBIND_C_UP = "CStickUp_";
- public static final String KEY_GCBIND_C_DOWN = "CStickDown_";
- public static final String KEY_GCBIND_C_LEFT = "CStickLeft_";
- public static final String KEY_GCBIND_C_RIGHT = "CStickRight_";
- public static final String KEY_GCBIND_TRIGGER_L = "InputL_";
- public static final String KEY_GCBIND_TRIGGER_R = "InputR_";
- public static final String KEY_GCBIND_DPAD_UP = "DPadUp_";
- public static final String KEY_GCBIND_DPAD_DOWN = "DPadDown_";
- public static final String KEY_GCBIND_DPAD_LEFT = "DPadLeft_";
- public static final String KEY_GCBIND_DPAD_RIGHT = "DPadRight_";
-
- public static final String KEY_EMU_RUMBLE = "EmuRumble";
-
- public static final String KEY_WIIMOTE_EXTENSION = "Extension";
-
- // Controller keys for game specific settings
- public static final String KEY_WIIMOTE_PROFILE = "WiimoteProfile";
-
- public static final String KEY_WIIBIND_A = "WiimoteA_";
- public static final String KEY_WIIBIND_B = "WiimoteB_";
- public static final String KEY_WIIBIND_1 = "Wiimote1_";
- public static final String KEY_WIIBIND_2 = "Wiimote2_";
- public static final String KEY_WIIBIND_MINUS = "WiimoteMinus_";
- public static final String KEY_WIIBIND_PLUS = "WiimotePlus_";
- public static final String KEY_WIIBIND_HOME = "WiimoteHome_";
- public static final String KEY_WIIBIND_IR_UP = "IRUp_";
- public static final String KEY_WIIBIND_IR_DOWN = "IRDown_";
- public static final String KEY_WIIBIND_IR_LEFT = "IRLeft_";
- public static final String KEY_WIIBIND_IR_RIGHT = "IRRight_";
- public static final String KEY_WIIBIND_IR_FORWARD = "IRForward_";
- public static final String KEY_WIIBIND_IR_BACKWARD = "IRBackward_";
- public static final String KEY_WIIBIND_IR_HIDE = "IRHide_";
- public static final String KEY_WIIBIND_IR_PITCH = "IRTotalPitch";
- public static final String KEY_WIIBIND_IR_YAW = "IRTotalYaw";
- public static final String KEY_WIIBIND_IR_VERTICAL_OFFSET = "IRVerticalOffset";
- public static final String KEY_WIIBIND_SWING_UP = "SwingUp_";
- public static final String KEY_WIIBIND_SWING_DOWN = "SwingDown_";
- public static final String KEY_WIIBIND_SWING_LEFT = "SwingLeft_";
- public static final String KEY_WIIBIND_SWING_RIGHT = "SwingRight_";
- public static final String KEY_WIIBIND_SWING_FORWARD = "SwingForward_";
- public static final String KEY_WIIBIND_SWING_BACKWARD = "SwingBackward_";
- public static final String KEY_WIIBIND_TILT_FORWARD = "TiltForward_";
- public static final String KEY_WIIBIND_TILT_BACKWARD = "TiltBackward_";
- public static final String KEY_WIIBIND_TILT_LEFT = "TiltLeft_";
- public static final String KEY_WIIBIND_TILT_RIGHT = "TiltRight_";
- public static final String KEY_WIIBIND_TILT_MODIFIER = "TiltModifier_";
- public static final String KEY_WIIBIND_SHAKE_X = "ShakeX_";
- public static final String KEY_WIIBIND_SHAKE_Y = "ShakeY_";
- public static final String KEY_WIIBIND_SHAKE_Z = "ShakeZ_";
- public static final String KEY_WIIBIND_DPAD_UP = "WiimoteUp_";
- public static final String KEY_WIIBIND_DPAD_DOWN = "WiimoteDown_";
- public static final String KEY_WIIBIND_DPAD_LEFT = "WiimoteLeft_";
- public static final String KEY_WIIBIND_DPAD_RIGHT = "WiimoteRight_";
- public static final String KEY_WIIBIND_NUNCHUK_C = "NunchukC_";
- public static final String KEY_WIIBIND_NUNCHUK_Z = "NunchukZ_";
- public static final String KEY_WIIBIND_NUNCHUK_UP = "NunchukUp_";
- public static final String KEY_WIIBIND_NUNCHUK_DOWN = "NunchukDown_";
- public static final String KEY_WIIBIND_NUNCHUK_LEFT = "NunchukLeft_";
- public static final String KEY_WIIBIND_NUNCHUK_RIGHT = "NunchukRight_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_UP = "NunchukSwingUp_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_DOWN = "NunchukSwingDown_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_LEFT = "NunchukSwingLeft_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_RIGHT = "NunchukSwingRight_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_FORWARD = "NunchukSwingForward_";
- public static final String KEY_WIIBIND_NUNCHUK_SWING_BACKWARD = "NunchukSwingBackward_";
- public static final String KEY_WIIBIND_NUNCHUK_TILT_FORWARD = "NunchukTiltForward_";
- public static final String KEY_WIIBIND_NUNCHUK_TILT_BACKWARD = "NunchukTiltBackward_";
- public static final String KEY_WIIBIND_NUNCHUK_TILT_LEFT = "NunchukTiltLeft_";
- public static final String KEY_WIIBIND_NUNCHUK_TILT_RIGHT = "NunchukTiltRight_";
- public static final String KEY_WIIBIND_NUNCHUK_TILT_MODIFIER = "NunchukTiltModifier_";
- public static final String KEY_WIIBIND_NUNCHUK_SHAKE_X = "NunchukShakeX_";
- public static final String KEY_WIIBIND_NUNCHUK_SHAKE_Y = "NunchukShakeY_";
- public static final String KEY_WIIBIND_NUNCHUK_SHAKE_Z = "NunchukShakeZ_";
- public static final String KEY_WIIBIND_CLASSIC_A = "ClassicA_";
- public static final String KEY_WIIBIND_CLASSIC_B = "ClassicB_";
- public static final String KEY_WIIBIND_CLASSIC_X = "ClassicX_";
- public static final String KEY_WIIBIND_CLASSIC_Y = "ClassicY_";
- public static final String KEY_WIIBIND_CLASSIC_ZL = "ClassicZL_";
- public static final String KEY_WIIBIND_CLASSIC_ZR = "ClassicZR_";
- public static final String KEY_WIIBIND_CLASSIC_MINUS = "ClassicMinus_";
- public static final String KEY_WIIBIND_CLASSIC_PLUS = "ClassicPlus_";
- public static final String KEY_WIIBIND_CLASSIC_HOME = "ClassicHome_";
- public static final String KEY_WIIBIND_CLASSIC_LEFT_UP = "ClassicLeftStickUp_";
- public static final String KEY_WIIBIND_CLASSIC_LEFT_DOWN = "ClassicLeftStickDown_";
- public static final String KEY_WIIBIND_CLASSIC_LEFT_LEFT = "ClassicLeftStickLeft_";
- public static final String KEY_WIIBIND_CLASSIC_LEFT_RIGHT = "ClassicLeftStickRight_";
- public static final String KEY_WIIBIND_CLASSIC_RIGHT_UP = "ClassicRightStickUp_";
- public static final String KEY_WIIBIND_CLASSIC_RIGHT_DOWN = "ClassicRightStickDown_";
- public static final String KEY_WIIBIND_CLASSIC_RIGHT_LEFT = "ClassicRightStickLeft_";
- public static final String KEY_WIIBIND_CLASSIC_RIGHT_RIGHT = "ClassicRightStickRight_";
- public static final String KEY_WIIBIND_CLASSIC_TRIGGER_L = "ClassicTriggerL_";
- public static final String KEY_WIIBIND_CLASSIC_TRIGGER_R = "ClassicTriggerR_";
- public static final String KEY_WIIBIND_CLASSIC_DPAD_UP = "ClassicUp_";
- public static final String KEY_WIIBIND_CLASSIC_DPAD_DOWN = "ClassicDown_";
- public static final String KEY_WIIBIND_CLASSIC_DPAD_LEFT = "ClassicLeft_";
- public static final String KEY_WIIBIND_CLASSIC_DPAD_RIGHT = "ClassicRight_";
- public static final String KEY_WIIBIND_GUITAR_FRET_GREEN = "GuitarGreen_";
- public static final String KEY_WIIBIND_GUITAR_FRET_RED = "GuitarRed_";
- public static final String KEY_WIIBIND_GUITAR_FRET_YELLOW = "GuitarYellow_";
- public static final String KEY_WIIBIND_GUITAR_FRET_BLUE = "GuitarBlue_";
- public static final String KEY_WIIBIND_GUITAR_FRET_ORANGE = "GuitarOrange_";
- public static final String KEY_WIIBIND_GUITAR_STRUM_UP = "GuitarStrumUp_";
- public static final String KEY_WIIBIND_GUITAR_STRUM_DOWN = "GuitarStrumDown_";
- public static final String KEY_WIIBIND_GUITAR_MINUS = "GuitarMinus_";
- public static final String KEY_WIIBIND_GUITAR_PLUS = "GuitarPlus_";
- public static final String KEY_WIIBIND_GUITAR_STICK_UP = "GuitarUp_";
- public static final String KEY_WIIBIND_GUITAR_STICK_DOWN = "GuitarDown_";
- public static final String KEY_WIIBIND_GUITAR_STICK_LEFT = "GuitarLeft_";
- public static final String KEY_WIIBIND_GUITAR_STICK_RIGHT = "GuitarRight_";
- public static final String KEY_WIIBIND_GUITAR_WHAMMY_BAR = "GuitarWhammy_";
- public static final String KEY_WIIBIND_DRUMS_PAD_RED = "DrumsRed_";
- public static final String KEY_WIIBIND_DRUMS_PAD_YELLOW = "DrumsYellow_";
- public static final String KEY_WIIBIND_DRUMS_PAD_BLUE = "DrumsBlue_";
- public static final String KEY_WIIBIND_DRUMS_PAD_GREEN = "DrumsGreen_";
- public static final String KEY_WIIBIND_DRUMS_PAD_ORANGE = "DrumsOrange_";
- public static final String KEY_WIIBIND_DRUMS_PAD_BASS = "DrumsBass_";
- public static final String KEY_WIIBIND_DRUMS_MINUS = "DrumsMinus_";
- public static final String KEY_WIIBIND_DRUMS_PLUS = "DrumsPlus_";
- public static final String KEY_WIIBIND_DRUMS_STICK_UP = "DrumsUp_";
- public static final String KEY_WIIBIND_DRUMS_STICK_DOWN = "DrumsDown_";
- public static final String KEY_WIIBIND_DRUMS_STICK_LEFT = "DrumsLeft_";
- public static final String KEY_WIIBIND_DRUMS_STICK_RIGHT = "DrumsRight_";
- public static final String KEY_WIIBIND_TURNTABLE_GREEN_LEFT = "TurntableGreenLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_RED_LEFT = "TurntableRedLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_BLUE_LEFT = "TurntableBlueLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_GREEN_RIGHT = "TurntableGreenRight_";
- public static final String KEY_WIIBIND_TURNTABLE_RED_RIGHT = "TurntableRedRight_";
- public static final String KEY_WIIBIND_TURNTABLE_BLUE_RIGHT = "TurntableBlueRight_";
- public static final String KEY_WIIBIND_TURNTABLE_MINUS = "TurntableMinus_";
- public static final String KEY_WIIBIND_TURNTABLE_PLUS = "TurntablePlus_";
- public static final String KEY_WIIBIND_TURNTABLE_EUPHORIA = "TurntableEuphoria_";
- public static final String KEY_WIIBIND_TURNTABLE_LEFT_LEFT = "TurntableLeftLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_LEFT_RIGHT = "TurntableLeftRight_";
- public static final String KEY_WIIBIND_TURNTABLE_RIGHT_LEFT = "TurntableRightLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_RIGHT_RIGHT = "TurntableRightRight_";
- public static final String KEY_WIIBIND_TURNTABLE_STICK_UP = "TurntableUp_";
- public static final String KEY_WIIBIND_TURNTABLE_STICK_DOWN = "TurntableDown_";
- public static final String KEY_WIIBIND_TURNTABLE_STICK_LEFT = "TurntableLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_STICK_RIGHT = "TurntableRight_";
- public static final String KEY_WIIBIND_TURNTABLE_EFFECT_DIAL = "TurntableEffDial_";
- public static final String KEY_WIIBIND_TURNTABLE_CROSSFADE_LEFT = "TurntableCrossLeft_";
- public static final String KEY_WIIBIND_TURNTABLE_CROSSFADE_RIGHT = "TurntableCrossRight_";
-
private static BiMap<String, String> sectionsMap = new BiMap<>();
static
@@ -283,13 +128,4 @@ public final class SettingsFile
return new File(
DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini");
}
-
- public static File getWiiProfile(String profile)
- {
- String wiiConfigPath =
- DirectoryInitialization.getUserDirectory() + "/Config/Profiles/Wiimote/" +
- profile + ".ini";
-
- return new File(wiiConfigPath);
- }
}
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 142aca0641..27e425d8b6 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
@@ -115,7 +115,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
{
int overlayX = mInputOverlay.getLeft();
int overlayY = mInputOverlay.getTop();
- mInputOverlay.setSurfacePosition(new Rect(
+ mInputOverlay.setSurfacePosition(activity.getSettings(), new Rect(
surfaceView.getLeft() - overlayX, surfaceView.getTop() - overlayY,
surfaceView.getRight() - overlayX, surfaceView.getBottom() - overlayY));
});
@@ -133,6 +133,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
public void onResume()
{
super.onResume();
+
+ if (mInputOverlay != null && NativeLibrary.IsGameMetadataValid())
+ mInputOverlay.refreshControls(activity.getSettings());
+
run(activity.isActivityRecreated());
}
@@ -170,19 +174,19 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
.setBoolean(settings, !BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.getBoolean(settings));
if (mInputOverlay != null)
- mInputOverlay.refreshControls();
+ mInputOverlay.refreshControls(settings);
}
- public void initInputPointer()
+ public void initInputPointer(Settings settings)
{
if (mInputOverlay != null)
- mInputOverlay.initTouchPointer();
+ mInputOverlay.initTouchPointer(settings);
}
- public void refreshInputOverlay()
+ public void refreshInputOverlay(Settings settings)
{
if (mInputOverlay != null)
- mInputOverlay.refreshControls();
+ mInputOverlay.refreshControls(settings);
}
public void refreshOverlayPointer(Settings settings)
@@ -191,10 +195,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
mInputOverlay.refreshOverlayPointer(settings);
}
- public void resetInputOverlay()
+ public void resetInputOverlay(Settings settings)
{
if (mInputOverlay != null)
- mInputOverlay.resetButtonPlacement();
+ mInputOverlay.resetButtonPlacement(settings);
}
@Override
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java
index 7d949befed..f0a212248f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java
@@ -29,15 +29,17 @@ import androidx.preference.PreferenceManager;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting;
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider;
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider.ControlId;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
+import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
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.utils.SettingsFile;
-import org.dolphinemu.dolphinemu.utils.IniFile;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -66,9 +68,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
private Rect mSurfacePosition = null;
private boolean mIsFirstRun = true;
- private boolean mGameCubeRegistered = false;
- private boolean mWiiRegistered = false;
+ private boolean[] mGcPadRegistered = new boolean[4];
+ private boolean[] mWiimoteRegistered = new boolean[4];
private boolean mIsInEditMode = false;
+ private int mControllerType = -1;
+ private int mControllerIndex = 0;
private InputOverlayDrawableButton mButtonBeingConfigured;
private InputOverlayDrawableDpad mDpadBeingConfigured;
private InputOverlayDrawableJoystick mJoystickBeingConfigured;
@@ -131,10 +135,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
if (!mPreferences.getBoolean("OverlayInitV3", false))
defaultOverlay();
- // Load the controls if we can. If not, EmulationActivity has to do it later.
- if (NativeLibrary.IsGameMetadataValid())
- refreshControls();
-
// Set the on touch listener.
setOnTouchListener(this);
@@ -145,13 +145,13 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
requestFocus();
}
- public void setSurfacePosition(Rect rect)
+ public void setSurfacePosition(Settings settings, Rect rect)
{
mSurfacePosition = rect;
- initTouchPointer();
+ initTouchPointer(settings);
}
- public void initTouchPointer()
+ public void initTouchPointer(Settings settings)
{
// Check if we have all the data we need yet
boolean aspectRatioAvailable = NativeLibrary.IsRunningAndStarted();
@@ -164,7 +164,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
int doubleTapButton = IntSetting.MAIN_DOUBLE_TAP_BUTTON.getIntGlobal();
- if (getConfiguredControllerType() != InputOverlay.OVERLAY_WIIMOTE_CLASSIC &&
+ if (getConfiguredControllerType(settings) != InputOverlay.OVERLAY_WIIMOTE_CLASSIC &&
doubleTapButton == ButtonType.CLASSIC_BUTTON_A)
{
doubleTapButton = ButtonType.WIIMOTE_BUTTON_A;
@@ -186,7 +186,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
overlayPointer = new InputOverlayPointer(mSurfacePosition, doubleTapControl,
IntSetting.MAIN_IR_MODE.getIntGlobal(),
- BooleanSetting.MAIN_IR_ALWAYS_RECENTER.getBooleanGlobal());
+ BooleanSetting.MAIN_IR_ALWAYS_RECENTER.getBooleanGlobal(), mControllerIndex);
}
@Override
@@ -239,11 +239,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
button.setPressedState(true);
button.setTrackId(event.getPointerId(pointerIndex));
pressed = true;
- InputOverrider.setControlState(0, button.getControl(), 1.0);
+ InputOverrider.setControlState(mControllerIndex, button.getControl(), 1.0);
int analogControl = getAnalogControlForTrigger(button.getControl());
if (analogControl >= 0)
- InputOverrider.setControlState(0, analogControl, 1.0);
+ InputOverrider.setControlState(mControllerIndex, analogControl, 1.0);
}
break;
case MotionEvent.ACTION_UP:
@@ -252,11 +252,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
if (button.getTrackId() == event.getPointerId(pointerIndex))
{
button.setPressedState(false);
- InputOverrider.setControlState(0, button.getControl(), 0.0);
+ InputOverrider.setControlState(mControllerIndex, button.getControl(), 0.0);
int analogControl = getAnalogControlForTrigger(button.getControl());
if (analogControl >= 0)
- InputOverrider.setControlState(0, analogControl, 0.0);
+ InputOverrider.setControlState(mControllerIndex, analogControl, 0.0);
button.setTrackId(-1);
}
@@ -298,7 +298,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
{
if (!dpadPressed[i])
{
- InputOverrider.setControlState(0, dpad.getControl(i), 0.0);
+ InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 0.0);
}
}
// Press buttons
@@ -306,7 +306,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
{
if (dpadPressed[i])
{
- InputOverrider.setControlState(0, dpad.getControl(i), 1.0);
+ InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 1.0);
}
}
setDpadState(dpad, dpadPressed[0], dpadPressed[1], dpadPressed[2], dpadPressed[3]);
@@ -320,7 +320,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
for (int i = 0; i < 4; i++)
{
dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT);
- InputOverrider.setControlState(0, dpad.getControl(i), 0.0);
+ InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 0.0);
}
dpad.setTrackId(-1);
}
@@ -336,16 +336,18 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
pressed = true;
}
- InputOverrider.setControlState(0, joystick.getXControl(), joystick.getX());
- InputOverrider.setControlState(0, joystick.getYControl(), -joystick.getY());
+ InputOverrider.setControlState(mControllerIndex, joystick.getXControl(), joystick.getX());
+ InputOverrider.setControlState(mControllerIndex, joystick.getYControl(), -joystick.getY());
}
// No button/joystick pressed, safe to move pointer
if (!pressed && overlayPointer != null)
{
overlayPointer.onTouch(event);
- InputOverrider.setControlState(0, ControlId.WIIMOTE_IR_X, overlayPointer.getX());
- InputOverrider.setControlState(0, ControlId.WIIMOTE_IR_Y, -overlayPointer.getY());
+ InputOverrider.setControlState(mControllerIndex, ControlId.WIIMOTE_IR_X,
+ overlayPointer.getX());
+ InputOverrider.setControlState(mControllerIndex, ControlId.WIIMOTE_IR_Y,
+ -overlayPointer.getY());
}
invalidate();
@@ -359,7 +361,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
int fingerPositionX = (int) event.getX(pointerIndex);
int fingerPositionY = (int) event.getY(pointerIndex);
- int controller = getConfiguredControllerType();
String orientation =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
"-Portrait" : "";
@@ -398,7 +399,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// Persist button position by saving new place.
saveControlPosition(mButtonBeingConfigured.getLegacyId(),
mButtonBeingConfigured.getBounds().left,
- mButtonBeingConfigured.getBounds().top, controller, orientation);
+ mButtonBeingConfigured.getBounds().top, orientation);
mButtonBeingConfigured = null;
}
break;
@@ -436,7 +437,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// Persist button position by saving new place.
saveControlPosition(mDpadBeingConfigured.getLegacyId(),
mDpadBeingConfigured.getBounds().left, mDpadBeingConfigured.getBounds().top,
- controller, orientation);
+ orientation);
mDpadBeingConfigured = null;
}
break;
@@ -469,7 +470,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
{
saveControlPosition(mJoystickBeingConfigured.getLegacyId(),
mJoystickBeingConfigured.getBounds().left,
- mJoystickBeingConfigured.getBounds().top, controller, orientation);
+ mJoystickBeingConfigured.getBounds().top, orientation);
mJoystickBeingConfigured = null;
}
break;
@@ -486,14 +487,20 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
private void unregisterControllers()
{
- if (mGameCubeRegistered)
- InputOverrider.unregisterGameCube(0);
+ for (int i = 0; i < mGcPadRegistered.length; i++)
+ {
+ if (mGcPadRegistered[i])
+ InputOverrider.unregisterGameCube(i);
+ }
- if (mWiiRegistered)
- InputOverrider.unregisterWii(0);
+ for (int i = 0; i < mWiimoteRegistered.length; i++)
+ {
+ if (mWiimoteRegistered[i])
+ InputOverrider.unregisterWii(i);
+ }
- mGameCubeRegistered = false;
- mWiiRegistered = false;
+ Arrays.fill(mGcPadRegistered, false);
+ Arrays.fill(mWiimoteRegistered, false);
}
private int getAnalogControlForTrigger(int control)
@@ -785,7 +792,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
}
}
- public void refreshControls()
+ public void refreshControls(Settings settings)
{
unregisterControllers();
@@ -798,67 +805,60 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
"-Portrait" : "";
+ mControllerType = getConfiguredControllerType(settings);
+
+ IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ?
+ IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER;
+ int controllerIndex = controllerSetting.getInt(settings);
+
if (BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.getBooleanGlobal())
{
// Add all the enabled overlay items back to the HashSet.
- if (!NativeLibrary.IsEmulatingWii())
+ switch (mControllerType)
{
- IniFile dolphinIni = new IniFile(SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN));
+ case OVERLAY_GAMECUBE:
+ if (IntSetting.getSettingForSIDevice(controllerIndex).getInt(settings) ==
+ DISABLED_GAMECUBE_CONTROLLER && mIsFirstRun)
+ {
+ Toast.makeText(getContext(), R.string.disabled_gc_overlay_notice, Toast.LENGTH_SHORT)
+ .show();
+ }
- switch (dolphinIni.getInt(Settings.SECTION_INI_CORE, SettingsFile.KEY_GCPAD_PLAYER_1,
- EMULATED_GAMECUBE_CONTROLLER))
- {
- case DISABLED_GAMECUBE_CONTROLLER:
- if (mIsFirstRun)
- {
- Toast.makeText(getContext(), R.string.disabled_gc_overlay_notice, Toast.LENGTH_SHORT)
- .show();
- }
- break;
+ mControllerIndex = controllerIndex;
+ InputOverrider.registerGameCube(mControllerIndex);
+ mGcPadRegistered[mControllerIndex] = true;
- case EMULATED_GAMECUBE_CONTROLLER:
- InputOverrider.registerGameCube(0);
- mGameCubeRegistered = true;
- addGameCubeOverlayControls(orientation);
- break;
+ addGameCubeOverlayControls(orientation);
+ break;
- case GAMECUBE_ADAPTER:
- break;
- }
- }
- else
- {
- switch (getConfiguredControllerType())
- {
- case OVERLAY_GAMECUBE:
- InputOverrider.registerGameCube(0);
- mGameCubeRegistered = true;
- addGameCubeOverlayControls(orientation);
- break;
-
- case OVERLAY_WIIMOTE:
- case OVERLAY_WIIMOTE_SIDEWAYS:
- InputOverrider.registerWii(0);
- mWiiRegistered = true;
- addWiimoteOverlayControls(orientation);
- break;
-
- case OVERLAY_WIIMOTE_NUNCHUK:
- InputOverrider.registerWii(0);
- mWiiRegistered = true;
- addWiimoteOverlayControls(orientation);
- addNunchukOverlayControls(orientation);
- break;
-
- case OVERLAY_WIIMOTE_CLASSIC:
- InputOverrider.registerWii(0);
- mWiiRegistered = true;
- addClassicOverlayControls(orientation);
- break;
-
- case OVERLAY_NONE:
- break;
- }
+ case OVERLAY_WIIMOTE:
+ case OVERLAY_WIIMOTE_SIDEWAYS:
+ mControllerIndex = controllerIndex - 4;
+ InputOverrider.registerWii(mControllerIndex);
+ mWiimoteRegistered[mControllerIndex] = true;
+
+ addWiimoteOverlayControls(orientation);
+ break;
+
+ case OVERLAY_WIIMOTE_NUNCHUK:
+ mControllerIndex = controllerIndex - 4;
+ InputOverrider.registerWii(mControllerIndex);
+ mWiimoteRegistered[mControllerIndex] = true;
+
+ addWiimoteOverlayControls(orientation);
+ addNunchukOverlayControls(orientation);
+ break;
+
+ case OVERLAY_WIIMOTE_CLASSIC:
+ mControllerIndex = controllerIndex - 4;
+ InputOverrider.registerWii(mControllerIndex);
+ mWiimoteRegistered[mControllerIndex] = true;
+
+ addClassicOverlayControls(orientation);
+ break;
+
+ case OVERLAY_NONE:
+ break;
}
}
@@ -875,20 +875,20 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
}
}
- public void resetButtonPlacement()
+ public void resetButtonPlacement(Settings settings)
{
boolean isLandscape =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
- // Values for these come from R.array.controllersEntries
- if (!NativeLibrary.IsEmulatingWii() || getConfiguredControllerType() == OVERLAY_GAMECUBE)
+ final int controller = getConfiguredControllerType(settings);
+ if (controller == OVERLAY_GAMECUBE)
{
if (isLandscape)
gcDefaultOverlay();
else
gcPortraitDefaultOverlay();
}
- else if (getConfiguredControllerType() == OVERLAY_WIIMOTE_CLASSIC)
+ else if (controller == OVERLAY_WIIMOTE_CLASSIC)
{
if (isLandscape)
wiiClassicDefaultOverlay();
@@ -908,27 +908,52 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
wiiOnlyPortraitDefaultOverlay();
}
}
- refreshControls();
+ refreshControls(settings);
}
- public static int getConfiguredControllerType(Context context)
+ public static int getConfiguredControllerType(Settings settings)
{
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getInt("wiiController", OVERLAY_WIIMOTE_NUNCHUK);
- }
+ IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ?
+ IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER;
+ int controllerIndex = controllerSetting.getInt(settings);
- private int getConfiguredControllerType()
- {
- return mPreferences.getInt("wiiController", OVERLAY_WIIMOTE_NUNCHUK);
+ if (controllerIndex >= 0 && controllerIndex < 4)
+ {
+ // GameCube controller
+ if (IntSetting.getSettingForSIDevice(controllerIndex).getInt(settings) == 6)
+ return OVERLAY_GAMECUBE;
+ }
+ else if (controllerIndex >= 4 && controllerIndex < 8)
+ {
+ // Wii Remote
+ int wiimoteIndex = controllerIndex - 4;
+ if (IntSetting.getSettingForWiimoteSource(wiimoteIndex).getInt(settings) == 1)
+ {
+ int attachmentIndex = EmulatedController.getSelectedWiimoteAttachment(wiimoteIndex);
+ switch (attachmentIndex)
+ {
+ case 1:
+ return OVERLAY_WIIMOTE_NUNCHUK;
+ case 2:
+ return OVERLAY_WIIMOTE_CLASSIC;
+ }
+
+ NumericSetting sidewaysSetting = EmulatedController.getSidewaysWiimoteSetting(wiimoteIndex);
+ boolean sideways = new InputMappingBooleanSetting(sidewaysSetting).getBoolean(settings);
+
+ return sideways ? OVERLAY_WIIMOTE_SIDEWAYS : OVERLAY_WIIMOTE;
+ }
+ }
+
+ return OVERLAY_NONE;
}
- private void saveControlPosition(int sharedPrefsId, int x, int y, int controller,
- String orientation)
+ private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation)
{
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
SharedPreferences.Editor sPrefsEditor = sPrefs.edit();
- sPrefsEditor.putFloat(getXKey(sharedPrefsId, controller, orientation), x);
- sPrefsEditor.putFloat(getYKey(sharedPrefsId, controller, orientation), y);
+ sPrefsEditor.putFloat(getXKey(sharedPrefsId, mControllerType, orientation), x);
+ sPrefsEditor.putFloat(getYKey(sharedPrefsId, mControllerType, orientation), y);
sPrefsEditor.apply();
}
@@ -989,7 +1014,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
* @param control Control identifier for the button the InputOverlayDrawableButton represents.
* @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set.
*/
- private static InputOverlayDrawableButton initializeOverlayButton(Context context,
+ private InputOverlayDrawableButton initializeOverlayButton(Context context,
int defaultResId, int pressedResId, int legacyId, int control, String orientation)
{
// Resources handle for fetching the initial Drawable resource.
@@ -997,7 +1022,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
- int controller = getConfiguredControllerType(context);
// Decide scale based on button ID and user preference
float scale;
@@ -1023,7 +1047,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
break;
case ButtonType.WIIMOTE_BUTTON_1:
case ButtonType.WIIMOTE_BUTTON_2:
- if (controller == 2)
+ if (mControllerType == OVERLAY_WIIMOTE_SIDEWAYS)
scale = 0.14f;
else
scale = 0.0875f;
@@ -1061,8 +1085,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
- int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f);
- int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f);
+ int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f);
+ int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f);
int width = overlayDrawable.getWidth();
int height = overlayDrawable.getHeight();
@@ -1092,7 +1116,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
* @param rightControl Control identifier for the right button.
* @return the initialized {@link InputOverlayDrawableDpad}
*/
- private static InputOverlayDrawableDpad initializeOverlayDpad(Context context,
+ private InputOverlayDrawableDpad initializeOverlayDpad(Context context,
int defaultResId,
int pressedOneDirectionResId,
int pressedTwoDirectionsResId,
@@ -1108,7 +1132,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
- int controller = getConfiguredControllerType(context);
// Decide scale based on button ID and user preference
float scale;
@@ -1122,7 +1145,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
scale = 0.275f;
break;
default:
- if (controller == 2 || controller == 1)
+ if (mControllerType == OVERLAY_WIIMOTE_SIDEWAYS || mControllerType == OVERLAY_WIIMOTE)
scale = 0.275f;
else
scale = 0.2125f;
@@ -1148,8 +1171,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
// These were set in the input overlay configuration menu.
- int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f);
- int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f);
+ int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f);
+ int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f);
int width = overlayDrawable.getWidth();
int height = overlayDrawable.getHeight();
@@ -1177,16 +1200,15 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
* @param yControl Control identifier for the Y axis.
* @return the initialized {@link InputOverlayDrawableJoystick}.
*/
- private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context,
- int resOuter, int defaultResInner, int pressedResInner, int legacyId, int xControl,
- int yControl, String orientation)
+ private InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, int resOuter,
+ int defaultResInner, int pressedResInner, int legacyId, int xControl, int yControl,
+ String orientation)
{
// Resources handle for fetching the initial Drawable resource.
final Resources res = context.getResources();
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
- int controller = getConfiguredControllerType(context);
// Decide scale based on user preference
float scale = 0.275f;
@@ -1201,8 +1223,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
- int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f);
- int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f);
+ int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f);
+ int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f);
// Decide inner scale based on joystick ID
float innerScale;
@@ -1225,7 +1247,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
// Send the drawableId to the joystick so it can be referenced when saving control position.
final InputOverlayDrawableJoystick overlayDrawable =
new InputOverlayDrawableJoystick(res, bitmapOuter, bitmapInnerDefault,
- bitmapInnerPressed, outerRect, innerRect, legacyId, xControl, yControl);
+ bitmapInnerPressed, outerRect, innerRect, legacyId, xControl, yControl,
+ mControllerIndex);
// Need to set the image's position
overlayDrawable.setPosition(drawableX, drawableY);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java
index d580df7349..ee8c852610 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java
@@ -31,6 +31,7 @@ public final class InputOverlayDrawableJoystick
private int mPreviousTouchX, mPreviousTouchY;
private final int mWidth;
private final int mHeight;
+ private final int mControllerIndex;
private Rect mVirtBounds;
private Rect mOrigBounds;
private int mOpacity;
@@ -55,7 +56,7 @@ public final class InputOverlayDrawableJoystick
*/
public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, Bitmap bitmapInnerDefault,
Bitmap bitmapInnerPressed, Rect rectOuter, Rect rectInner, int legacyId, int xControl,
- int yControl)
+ int yControl, int controllerIndex)
{
mJoystickLegacyId = legacyId;
mJoystickXControl = xControl;
@@ -68,6 +69,10 @@ public final class InputOverlayDrawableJoystick
mWidth = bitmapOuter.getWidth();
mHeight = bitmapOuter.getHeight();
+ if (controllerIndex < 0 || controllerIndex >= 4)
+ throw new IllegalArgumentException("controllerIndex must be 0-3");
+ mControllerIndex = controllerIndex;
+
setBounds(rectOuter);
mDefaultStateInnerBitmap.setBounds(rectInner);
mPressedStateInnerBitmap.setBounds(rectInner);
@@ -221,7 +226,8 @@ public final class InputOverlayDrawableJoystick
double angle = Math.atan2(y, x) + Math.PI + Math.PI;
double radius = Math.hypot(y, x);
- double maxRadius = InputOverrider.getGateRadiusAtAngle(0, mJoystickXControl, angle);
+ double maxRadius = InputOverrider.getGateRadiusAtAngle(mControllerIndex, mJoystickXControl,
+ angle);
if (radius > maxRadius)
{
y = maxRadius * Math.sin(angle);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java
index 18de4f4b47..1d96f5c617 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java
@@ -32,6 +32,7 @@ public class InputOverlayPointer
private int mMode;
private boolean mRecenter;
+ private int mControllerIndex;
private boolean doubleTap = false;
private int mDoubleTapControl;
@@ -47,11 +48,13 @@ public class InputOverlayPointer
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.CLASSIC_BUTTON_A);
}
- public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter)
+ public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter,
+ int controllerIndex)
{
mDoubleTapControl = doubleTapControl;
mMode = mode;
mRecenter = recenter;
+ mControllerIndex = controllerIndex;
mGameCenterX = (surfacePosition.left + surfacePosition.right) / 2.0f;
mGameCenterY = (surfacePosition.top + surfacePosition.bottom) / 2.0f;
@@ -128,8 +131,9 @@ public class InputOverlayPointer
{
if (doubleTap)
{
- InputOverrider.setControlState(0, mDoubleTapControl, 1.0);
- new Handler().postDelayed(() -> InputOverrider.setControlState(0, mDoubleTapControl, 0.0),
+ InputOverrider.setControlState(mControllerIndex, mDoubleTapControl, 1.0);
+ new Handler().postDelayed(() -> InputOverrider.setControlState(mControllerIndex,
+ mDoubleTapControl, 0.0),
50);
}
else
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java
deleted file mode 100644
index 9e31a46a96..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.utils;
-
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-/**
- * Some controllers have incorrect mappings. This class has special-case fixes for them.
- */
-public class ControllerMappingHelper
-{
- /**
- * Some controllers report extra button presses that can be ignored.
- */
- public static boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode)
- {
- if (isDualShock4(inputDevice))
- {
- // The two analog triggers generate analog motion events as well as a keycode.
- // We always prefer to use the analog values, so throw away the button press
- return keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2;
- }
- return false;
- }
-
- /**
- * Scale an axis to be zero-centered with a proper range.
- */
- public static float scaleAxis(InputDevice inputDevice, int axis, float value)
- {
- if (isDualShock4(inputDevice))
- {
- // Android doesn't have correct mappings for this controller's triggers. It reports them
- // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
- // Scale them to properly zero-centered with a range of [0.0, 1.0].
- if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY)
- {
- return (value + 1) / 2.0f;
- }
- }
- else if (isXboxOneWireless(inputDevice))
- {
- // Same as the DualShock 4, the mappings are missing.
- if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ)
- {
- return (value + 1) / 2.0f;
- }
- }
- return value;
- }
-
- private static boolean isDualShock4(InputDevice inputDevice)
- {
- // Sony DualShock 4 controller
- return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc;
- }
-
- private static boolean isXboxOneWireless(InputDevice inputDevice)
- {
- // Microsoft Xbox One controller
- return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
index b1584df1be..bb2f45de43 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java
@@ -31,17 +31,21 @@ import java.io.InputStream;
import java.io.OutputStream;
/**
- * A service that spawns its own thread in order to copy several binary and shader files
- * from the Dolphin APK to the external file system.
+ * A class that spawns its own thread in order perform initialization.
+ *
+ * The initialization steps include:
+ * - Extracting the Sys directory from the APK so it can be accessed using regular file APIs
+ * - Letting the native code know where on external storage it should place the User directory
+ * - Running the native code's init steps (which include things like populating the User directory)
*/
public final class DirectoryInitialization
{
public static final String EXTRA_STATE = "directoryState";
- private static final int WiimoteNewVersion = 5; // Last changed in PR 8907
private static final MutableLiveData<DirectoryInitializationState> directoryState =
new MutableLiveData<>(DirectoryInitializationState.NOT_YET_INITIALIZED);
private static volatile boolean areDirectoriesAvailable = false;
private static String userPath;
+ private static String sysPath;
private static boolean isUsingLegacyUserDirectory = false;
public enum DirectoryInitializationState
@@ -73,8 +77,7 @@ public final class DirectoryInitialization
System.exit(1);
}
- initializeInternalStorage(context);
- boolean wiimoteIniWritten = initializeExternalStorage(context);
+ extractSysDirectory(context);
NativeLibrary.Initialize();
NativeLibrary.ReportStartToAnalytics();
@@ -82,13 +85,6 @@ public final class DirectoryInitialization
checkThemeSettings(context);
- if (wiimoteIniWritten)
- {
- // This has to be done after calling NativeLibrary.Initialize(),
- // as it relies on the config system
- EmulationActivity.updateWiimoteNewIniPreferences(context);
- }
-
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
}
@@ -131,7 +127,7 @@ public final class DirectoryInitialization
return true;
}
- private static void initializeInternalStorage(Context context)
+ private static void extractSysDirectory(Context context)
{
File sysDirectory = new File(context.getFilesDir(), "Sys");
@@ -142,7 +138,7 @@ public final class DirectoryInitialization
// There is no extracted Sys directory, or there is a Sys directory from another
// version of Dolphin that might contain outdated files. Let's (re-)extract Sys.
deleteDirectoryRecursively(sysDirectory);
- copyAssetFolder("Sys", sysDirectory, true, context);
+ copyAssetFolder("Sys", sysDirectory, context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("sysDirectoryVersion", revision);
@@ -150,46 +146,8 @@ public final class DirectoryInitialization
}
// Let the native code know where the Sys directory is.
- SetSysDirectory(sysDirectory.getPath());
- }
-
- // Returns whether the WiimoteNew.ini file was written to
- private static boolean initializeExternalStorage(Context context)
- {
- // Create User directory structure and copy some NAND files from the extracted Sys directory.
- CreateUserDirectories();
-
- // GCPadNew.ini and WiimoteNew.ini must contain specific values in order for controller
- // input to work as intended (they aren't user configurable), so we overwrite them just
- // in case the user has tried to modify them manually.
- //
- // ...Except WiimoteNew.ini contains the user configurable settings for Wii Remote
- // extensions in addition to all of its lines that aren't user configurable, so since we
- // don't want to lose the selected extensions, we don't overwrite that file if it exists.
- //
- // TODO: Redo the Android controller system so that we don't have to extract these INIs.
- String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config";
- String profileDirectory =
- NativeLibrary.GetUserDirectory() + File.separator + "Config/Profiles/Wiimote/";
- createWiimoteProfileDirectory(profileDirectory);
-
- copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true, context);
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- boolean overwriteWiimoteIni = prefs.getInt("WiimoteNewVersion", 0) != WiimoteNewVersion;
- boolean wiimoteIniWritten = copyAsset("WiimoteNew.ini",
- new File(configDirectory, "WiimoteNew.ini"), overwriteWiimoteIni, context);
- if (overwriteWiimoteIni)
- {
- SharedPreferences.Editor sPrefsEditor = prefs.edit();
- sPrefsEditor.putInt("WiimoteNewVersion", WiimoteNewVersion);
- sPrefsEditor.apply();
- }
-
- copyAsset("WiimoteProfile.ini", new File(profileDirectory, "WiimoteProfile.ini"), true,
- context);
-
- return wiimoteIniWritten;
+ sysPath = sysDirectory.getPath();
+ SetSysDirectory(sysPath);
}
private static void deleteDirectoryRecursively(@NonNull final File file)
@@ -240,26 +198,33 @@ public final class DirectoryInitialization
return userPath;
}
+ public static String getSysDirectory()
+ {
+ if (!areDirectoriesAvailable)
+ {
+ throw new IllegalStateException(
+ "DirectoryInitialization must run before accessing the Sys directory!");
+ }
+ return sysPath;
+ }
+
public static File getGameListCache(Context context)
{
return new File(context.getExternalCacheDir(), "gamelist.cache");
}
- private static boolean copyAsset(String asset, File output, Boolean overwrite, Context context)
+ private static boolean copyAsset(String asset, File output, Context context)
{
Log.verbose("[DirectoryInitialization] Copying File " + asset + " to " + output);
try
{
- if (!output.exists() || overwrite)
+ try (InputStream in = context.getAssets().open(asset))
{
- try (InputStream in = context.getAssets().open(asset))
+ try (OutputStream out = new FileOutputStream(output))
{
- try (OutputStream out = new FileOutputStream(output))
- {
- copyFile(in, out);
- return true;
- }
+ copyFile(in, out);
+ return true;
}
}
}
@@ -271,8 +236,7 @@ public final class DirectoryInitialization
return false;
}
- private static void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite,
- Context context)
+ private static void copyAssetFolder(String assetFolder, File outputFolder, Context context)
{
Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " +
outputFolder);
@@ -299,9 +263,8 @@ public final class DirectoryInitialization
createdFolder = true;
}
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file),
- overwrite, context);
- copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite,
context);
+ copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), context);
}
}
catch (IOException e)
@@ -322,18 +285,6 @@ public final class DirectoryInitialization
}
}
- private static void createWiimoteProfileDirectory(String directory)
- {
- File wiiPath = new File(directory);
- if (!wiiPath.isDirectory())
- {
- if (!wiiPath.mkdirs())
- {
- Log.error("[DirectoryInitialization] Failed to create folder " + wiiPath.getAbsolutePath());
- }
- }
- }
-
public static boolean preferOldFolderPicker(Context context)
{
// As of January 2021, ACTION_OPEN_DOCUMENT_TREE seems to be broken on the Nvidia Shield TV
@@ -433,7 +384,5 @@ public final class DirectoryInitialization
}
}
- private static native void CreateUserDirectories();
-
private static native void SetSysDirectory(String path);
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java
new file mode 100644
index 0000000000..8a67ec7e89
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.utils;
+
+import android.os.Looper;
+
+public class LooperThread extends Thread
+{
+ private Looper mLooper;
+
+ public LooperThread()
+ {
+ super();
+ }
+
+ public LooperThread(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void run()
+ {
+ Looper.prepare();
+
+ synchronized (this)
+ {
+ mLooper = Looper.myLooper();
+ notifyAll();
+ }
+
+ Looper.loop();
+ }
+
+ public Looper getLooper()
+ {
+ if (!isAlive())
+ {
+ throw new IllegalStateException();
+ }
+
+ synchronized (this)
+ {
+ while (mLooper == null)
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException ignored)
+ {
+ }
+ }
+ }
+
+ return mLooper;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java
deleted file mode 100644
index 0086f3beb5..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.view.Surface;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType;
-
-public class MotionListener implements SensorEventListener
-{
- private final Activity mActivity;
- private final SensorManager mSensorManager;
- private final Sensor mAccelSensor;
- private final Sensor mGyroSensor;
-
- private boolean mEnabled = false;
-
- // The same sampling period as for Wii Remotes
- private static final int SAMPLING_PERIOD_US = 1000000 / 200;
-
- public MotionListener(Activity activity)
- {
- mActivity = activity;
- mSensorManager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE);
- mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
- }
-
- @Override
- public void onSensorChanged(SensorEvent sensorEvent)
- {
- float x, y;
- float z = sensorEvent.values[2];
- int orientation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
- switch (orientation)
- {
- default:
- case Surface.ROTATION_0:
- x = -sensorEvent.values[0];
- y = -sensorEvent.values[1];
- break;
- case Surface.ROTATION_90:
- x = sensorEvent.values[1];
- y = -sensorEvent.values[0];
- break;
- case Surface.ROTATION_180:
- x = sensorEvent.values[0];
- y = sensorEvent.values[1];
- break;
- case Surface.ROTATION_270:
- x = -sensorEvent.values[1];
- y = sensorEvent.values[0];
- break;
- }
-
- if (sensorEvent.sensor == mAccelSensor)
- {
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_LEFT, x);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_RIGHT, x);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_FORWARD, y);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_BACKWARD, y);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_UP, z);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_ACCEL_DOWN, z);
- }
-
- if (sensorEvent.sensor == mGyroSensor)
- {
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_PITCH_UP, x);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_PITCH_DOWN, x);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_ROLL_LEFT, y);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_ROLL_RIGHT, y);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_YAW_LEFT, z);
- NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice,
- ButtonType.WIIMOTE_GYRO_YAW_RIGHT, z);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int i)
- {
- // We don't care about this
- }
-
- public void enable()
- {
- if (mEnabled)
- return;
-
- if (mAccelSensor != null)
- mSensorManager.registerListener(this, mAccelSensor, SAMPLING_PERIOD_US);
- if (mGyroSensor != null)
- mSensorManager.registerListener(this, mGyroSensor, SAMPLING_PERIOD_US);
-
- NativeLibrary.SetMotionSensorsEnabled(mAccelSensor != null, mGyroSensor != null);
-
- mEnabled = true;
- }
-
- public void disable()
- {
- if (!mEnabled)
- return;
-
- mSensorManager.unregisterListener(this);
-
- NativeLibrary.SetMotionSensorsEnabled(false, false);
-
- mEnabled = false;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java
deleted file mode 100644
index 27d556898a..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java
+++ /dev/null
@@ -1,97 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.utils;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.SparseArray;
-import android.view.InputDevice;
-
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.features.settings.model.AdHocStringSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.Settings;
-import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
-
-public class Rumble
-{
- private static Vibrator phoneVibrator;
- private static final SparseArray<Vibrator> emuVibrators = new SparseArray<>();
-
- public static void initRumble(EmulationActivity activity)
- {
- clear();
-
- if (BooleanSetting.MAIN_PHONE_RUMBLE.getBooleanGlobal())
- {
- setPhoneVibrator(true, activity);
- }
-
- for (int i = 0; i < 8; i++)
- {
- String deviceName = AdHocStringSetting.getStringGlobal(Settings.FILE_DOLPHIN,
- Settings.SECTION_BINDINGS, SettingsFile.KEY_EMU_RUMBLE + i, "");
-
- if (!deviceName.isEmpty())
- {
- for (int id : InputDevice.getDeviceIds())
- {
- InputDevice device = InputDevice.getDevice(id);
- if (deviceName.equals(device.getDescriptor()))
- {
- Vibrator vib = device.getVibrator();
- if (vib != null && vib.hasVibrator())
- emuVibrators.put(i, vib);
- }
- }
- }
- }
- }
-
- public static void setPhoneVibrator(boolean set, EmulationActivity activity)
- {
- if (set)
- {
- Vibrator vib = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
- if (vib != null && vib.hasVibrator())
- phoneVibrator = vib;
- }
- else
- {
- phoneVibrator = null;
- }
- }
-
- private static void clear()
- {
- phoneVibrator = null;
- emuVibrators.clear();
- }
-
- public static void checkRumble(int padId, double state)
- {
- if (phoneVibrator != null)
- doRumble(phoneVibrator);
-
- if (emuVibrators.get(padId) != null)
- doRumble(emuVibrators.get(padId));
- }
-
- public static void doRumble(Vibrator vib)
- {
- // Check again that it exists and can vibrate
- if (vib != null && vib.hasVibrator())
- {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- {
- vib.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
- }
- else
- {
- vib.vibrate(100);
- }
- }
- }
-}