From 3499a416e7579fe31c6eaf340d05341d7a42e0ae Mon Sep 17 00:00:00 2001 From: zackhow Date: Mon, 1 Oct 2018 19:36:00 -0400 Subject: [PATCH] Android: Add controller rumble support Android can be funky with controller vibration. Of the three controlers I have that contain a vibrator(PS3, Xbox360, 2017 Shield controller), only the Xbox360 controller registered as having a vibrator. So YYMV depending on the driver support of the device. --- .../dolphinemu/dolphinemu/NativeLibrary.java | 23 +---- .../activities/EmulationActivity.java | 15 +++ .../dolphinemu/dialogs/MotionAlertDialog.java | 80 +++------------ .../model/view/InputBindingSetting.java | 53 +++++++++- .../model/view/RumbleBindingSetting.java | 75 ++++++++++++++ .../settings/model/view/SettingsItem.java | 1 + .../features/settings/ui/SettingsAdapter.java | 18 ++-- .../ui/SettingsFragmentPresenter.java | 15 +++ .../viewholder/RumbleBindingViewHolder.java | 53 ++++++++++ .../features/settings/utils/SettingsFile.java | 2 + .../dolphinemu/dolphinemu/utils/Rumble.java | 98 +++++++++++++++++++ .../app/src/main/res/values/strings.xml | 4 + 12 files changed, 339 insertions(+), 98 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java 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 acfac1e0f4b9..3536a1777603 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 @@ -7,15 +7,11 @@ package org.dolphinemu.dolphinemu; import android.app.AlertDialog; -import android.content.Context; -import android.os.Build; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.preference.PreferenceManager; import android.view.Surface; import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.utils.Log; +import org.dolphinemu.dolphinemu.utils.Rumble; import java.lang.ref.WeakReference; @@ -245,22 +241,7 @@ public static void rumble(int padID, double state) return; } - if (PreferenceManager.getDefaultSharedPreferences(emulationActivity) - .getBoolean("phoneRumble", true)) - { - Vibrator vibrator = (Vibrator) emulationActivity.getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator != null && vibrator.hasVibrator()) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - { - vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); - } - else - { - vibrator.vibrate(100); - } - } - } + Rumble.checkRumble(padID, state); } public static native String GetUserSetting(String gameID, String Section, String Key); 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 b4050dfd8141..2aad63d36540 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 @@ -50,6 +50,7 @@ import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; +import org.dolphinemu.dolphinemu.utils.Rumble; import org.dolphinemu.dolphinemu.utils.TvUtil; import java.lang.annotation.Retention; @@ -277,6 +278,7 @@ protected void onCreate(Bundle savedInstanceState) Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + Rumble.initRumble(this); setContentView(R.layout.activity_emulation); @@ -361,6 +363,13 @@ protected void restoreState(Bundle savedInstanceState) mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION); } + @Override + protected void onStop() + { + super.onStop(); + Rumble.clear(); + } + @Override public void onBackPressed() { @@ -693,6 +702,7 @@ private void toggleRumble(boolean state) final SharedPreferences.Editor editor = mPreferences.edit(); editor.putBoolean("phoneRumble", state); editor.apply(); + Rumble.setPhoneVibrator(state, this); } @@ -950,6 +960,11 @@ private void showSubMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad) .commit(); } + public boolean deviceHasTouchScreen() + { + return mDeviceHasTouchScreen; + } + public String getSelectedTitle() { return mSelectedTitle; 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 index 56339bba3cf7..cb0564d06ad3 100644 --- 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 @@ -3,14 +3,18 @@ import android.app.AlertDialog; import android.content.Context; import android.content.SharedPreferences; +import android.os.Vibrator; import android.preference.PreferenceManager; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; +import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper; import org.dolphinemu.dolphinemu.utils.Log; +import org.dolphinemu.dolphinemu.utils.Rumble; import org.dolphinemu.dolphinemu.utils.TvUtil; import java.util.ArrayList; @@ -49,7 +53,8 @@ public boolean onKeyEvent(int keyCode, KeyEvent event) case KeyEvent.ACTION_UP: if (!ControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode)) { - saveKeyInput(event); + setting.onKeyInput(event); + dismiss(); } // Even if we ignore the key, we still consume it. Thus return true regardless. return true; @@ -63,13 +68,11 @@ public boolean onKeyEvent(int keyCode, KeyEvent event) public boolean onKeyLongPress(int keyCode, KeyEvent event) { // Option to clear by long back is only needed on the TV interface - if (TvUtil.isLeanback(getContext())) + if (TvUtil.isLeanback(getContext()) && keyCode == KeyEvent.KEYCODE_BACK) { - if (keyCode == KeyEvent.KEYCODE_BACK) - { - clearBinding(); - return true; - } + setting.clearValue(); + dismiss(); + return true; } return super.onKeyLongPress(keyCode, event); } @@ -162,69 +165,10 @@ else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f) if (numMovedAxis == 1) { mWaitingForEvent = false; - saveMotionInput(input, lastMovedRange, lastMovedDir); + setting.onMotionInput(input, lastMovedRange, lastMovedDir); + dismiss(); } } - return true; } - - /** - * 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. - */ - private void saveKeyInput(KeyEvent keyEvent) - { - InputDevice device = keyEvent.getDevice(); - String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode(); - String uiString = device.getName() + ": Button " + keyEvent.getKeyCode(); - - saveInput(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 '+' - */ - private void saveMotionInput(InputDevice device, InputDevice.MotionRange motionRange, - char axisDir) - { - String bindStr = - "Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir; - String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir; - - saveInput(bindStr, uiString); - } - - /** - * Save the input string to settings and SharedPreferences, then dismiss this Dialog. - */ - private void saveInput(String bind, String ui) - { - setting.setValue(bind); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - SharedPreferences.Editor editor = preferences.edit(); - - editor.putString(setting.getKey(), ui); - editor.apply(); - - dismiss(); - } - - private void clearBinding() - { - setting.setValue(""); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - SharedPreferences.Editor editor = preferences.edit(); - editor.remove(setting.getKey()); - editor.apply(); - dismiss(); - } } 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 index 45cfd3c7e85d..e3850cb44789 100644 --- 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 @@ -1,9 +1,15 @@ package org.dolphinemu.dolphinemu.features.settings.model.view; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.InputDevice; +import android.view.KeyEvent; + +import org.dolphinemu.dolphinemu.DolphinApplication; import org.dolphinemu.dolphinemu.features.settings.model.Setting; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; -public final class InputBindingSetting extends SettingsItem +public class InputBindingSetting extends SettingsItem { public InputBindingSetting(String key, String section, int titleId, Setting setting) { @@ -21,6 +27,37 @@ public String getValue() return setting.getValue(); } + /** + * 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(KeyEvent keyEvent) + { + InputDevice device = keyEvent.getDevice(); + String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode(); + String uiString = device.getName() + ": Button " + keyEvent.getKeyCode(); + setValue(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(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(bindStr, uiString); + } + /** * Write a value to the backing string. If that string was previously null, * initializes a new one and returns it, so it can be added to the Hashmap. @@ -28,8 +65,15 @@ public String getValue() * @param bind The input that will be bound * @return null if overwritten successfully; otherwise, a newly created StringSetting. */ - public StringSetting setValue(String bind) + public StringSetting setValue(String bind, String ui) { + SharedPreferences + preferences = + PreferenceManager.getDefaultSharedPreferences(DolphinApplication.getAppContext()); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(getKey(), ui); + editor.apply(); + if (getSetting() == null) { StringSetting setting = new StringSetting(getKey(), getSection(), bind); @@ -44,6 +88,11 @@ public StringSetting setValue(String bind) } } + public void clearValue() + { + setValue("", ""); + } + @Override public int getType() { 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 new file mode 100644 index 000000000000..8dde9825d580 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java @@ -0,0 +1,75 @@ +package org.dolphinemu.dolphinemu.features.settings.model.view; + +import android.content.SharedPreferences; +import android.content.res.Resources; +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.Setting; +import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; +import org.dolphinemu.dolphinemu.utils.Rumble; + +public class RumbleBindingSetting extends InputBindingSetting +{ + + public RumbleBindingSetting(String key, String section, int titleId, Setting setting) + { + super(key, section, titleId, setting); + } + + @Override + public String getValue() + { + if (getSetting() == null) + { + return ""; + } + + StringSetting setting = (StringSetting) getSetting(); + return setting.getValue(); + } + + /** + * Just need the device when saving rumble. + */ + @Override + public void onKeyInput(KeyEvent keyEvent) + { + saveRumble(keyEvent.getDevice()); + } + + /** + * Just need the device when saving rumble. + */ + @Override + public void onMotionInput(InputDevice device, + InputDevice.MotionRange motionRange, + char axisDir) + { + saveRumble(device); + } + + private void saveRumble(InputDevice device) + { + Vibrator vibrator = device.getVibrator(); + if (vibrator != null && vibrator.hasVibrator()) + { + setValue(device.getDescriptor(), device.getName()); + Rumble.doRumble(vibrator); + } + else + { + setValue("", + DolphinApplication.getAppContext().getString(R.string.rumble_not_found)); + } + } + + @Override + public int getType() + { + return TYPE_RUMBLE_BINDING; + } +} 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 1ab8ee06c585..e3c798521fee 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 @@ -19,6 +19,7 @@ public abstract class SettingsItem public static final int TYPE_SUBMENU = 4; public static final int TYPE_INPUT_BINDING = 5; public static final int TYPE_STRING_SINGLE_CHOICE = 6; + public static final int TYPE_RUMBLE_BINDING = 7; private String mKey; private String mSection; 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 cbf53060eb5c..a2392caefc7c 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 @@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.CheckBoxSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; +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.SliderSetting; @@ -29,6 +30,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.CheckBoxSettingViewHolder; 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.RumbleBindingViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SliderViewHolder; @@ -90,6 +92,10 @@ public SettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) view = inflater.inflate(R.layout.list_item_setting, parent, false); return new InputBindingSettingViewHolder(view, this, mContext); + case SettingsItem.TYPE_RUMBLE_BINDING: + view = inflater.inflate(R.layout.list_item_setting, parent, false); + return new RumbleBindingViewHolder(view, this, mContext); + default: Log.error("[SettingsAdapter] Invalid view type: " + viewType); return null; @@ -216,19 +222,17 @@ public void onInputBindingClick(final InputBindingSetting item, final int positi { final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item); dialog.setTitle(R.string.input_binding); - dialog.setMessage(String.format(mContext.getString(R.string.input_binding_description), + dialog.setMessage(String.format(mContext.getString( + item instanceof RumbleBindingSetting ? + R.string.input_rumble_description : R.string.input_binding_description), mContext.getString(item.getNameId()))); dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this); dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), (dialogInterface, i) -> { - item.setValue(""); - - SharedPreferences sharedPreferences = + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.remove(item.getKey()); - editor.apply(); + item.clearValue(); }); dialog.setOnDismissListener(dialog1 -> { 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 03f6b9962a4b..a7759be7f34d 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 @@ -14,6 +14,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.CheckBoxSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.HeaderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; +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.SliderSetting; @@ -632,6 +633,8 @@ private void addGcPadSubSettings(ArrayList sl, int gcPadNumber, in bindingsSection.getSetting(SettingsFile.KEY_GCBIND_DPAD_LEFT + gcPadNumber); Setting bindDPadRight = bindingsSection.getSetting(SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber); + Setting gcEmuRumble = + bindingsSection.getSetting(SettingsFile.KEY_EMU_RUMBLE + gcPadNumber); sl.add(new HeaderSetting(null, null, R.string.generic_buttons, 0)); sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_A + gcPadNumber, @@ -682,6 +685,11 @@ private void addGcPadSubSettings(ArrayList sl, int gcPadNumber, in Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft)); sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber, Settings.SECTION_BINDINGS, R.string.generic_right, bindDPadRight)); + + + sl.add(new HeaderSetting(null, null, R.string.emulation_control_rumble, 0)); + sl.add(new RumbleBindingSetting(SettingsFile.KEY_EMU_RUMBLE + gcPadNumber, + Settings.SECTION_BINDINGS, R.string.emulation_control_rumble, gcEmuRumble)); } else // Adapter { @@ -761,6 +769,8 @@ private void addWiimoteSubSettings(ArrayList sl, int wiimoteNumber bindingsSection.getSetting(SettingsFile.KEY_WIIBIND_DPAD_LEFT + wiimoteNumber); Setting bindDPadRight = bindingsSection.getSetting(SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber); + Setting wiiEmuRumble = + bindingsSection.getSetting(SettingsFile.KEY_EMU_RUMBLE + wiimoteNumber); sl.add(new SingleChoiceSetting(SettingsFile.KEY_WIIMOTE_EXTENSION, Settings.SECTION_WIIMOTE + (wiimoteNumber - 3), R.string.wiimote_extensions, @@ -843,6 +853,11 @@ private void addWiimoteSubSettings(ArrayList sl, int wiimoteNumber Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft)); sl.add(new InputBindingSetting(SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber, Settings.SECTION_BINDINGS, R.string.generic_right, bindDPadRight)); + + + sl.add(new HeaderSetting(null, null, R.string.emulation_control_rumble, 0)); + sl.add(new RumbleBindingSetting(SettingsFile.KEY_EMU_RUMBLE + wiimoteNumber, + Settings.SECTION_BINDINGS, R.string.emulation_control_rumble, wiiEmuRumble)); } private void addExtensionTypeSettings(ArrayList sl, int wiimoteNumber, 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 new file mode 100644 index 000000000000..9720a1b9771c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java @@ -0,0 +1,53 @@ +package org.dolphinemu.dolphinemu.features.settings.ui.viewholder; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.View; +import android.widget.TextView; + +import org.dolphinemu.dolphinemu.R; +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 TextView mTextSettingName; + private TextView mTextSettingDescription; + + private Context mContext; + + public RumbleBindingViewHolder(View itemView, SettingsAdapter adapter, Context context) + { + super(itemView, adapter); + + mContext = context; + } + + @Override + protected void findViews(View root) + { + mTextSettingName = (TextView) root.findViewById(R.id.text_setting_name); + mTextSettingDescription = (TextView) root.findViewById(R.id.text_setting_description); + } + + @Override + public void bind(SettingsItem item) + { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); + + mItem = (RumbleBindingSetting) item; + + mTextSettingName.setText(item.getNameId()); + mTextSettingDescription.setText(sharedPreferences.getString(mItem.getKey(), "")); + } + + @Override + public void onClick(View clicked) + { + getAdapter().onInputBindingClick(mItem, getAdapterPosition()); + } +} 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 b1f6dc46f0dd..0ccf700b0e27 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 @@ -115,6 +115,8 @@ public final class SettingsFile public static final String KEY_GCADAPTER_RUMBLE = "AdapterRumble"; public static final String KEY_GCADAPTER_BONGOS = "SimulateKonga"; + public static final String KEY_EMU_RUMBLE = "EmuRumble"; + public static final String KEY_WIIMOTE_TYPE = "Source"; public static final String KEY_WIIMOTE_EXTENSION = "Extension"; 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 new file mode 100644 index 000000000000..7acc7c4a945d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java @@ -0,0 +1,98 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.content.Context; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.preference.PreferenceManager; +import android.util.SparseArray; +import android.view.InputDevice; + +import org.dolphinemu.dolphinemu.activities.EmulationActivity; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; +import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; +import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; + +import java.util.HashMap; + +public class Rumble +{ + private static Vibrator phoneVibrator; + private static SparseArray emuVibrators; + + public static void initRumble(EmulationActivity activity) + { + if (activity.deviceHasTouchScreen() && + PreferenceManager.getDefaultSharedPreferences(activity) + .getBoolean("phoneRumble", true)) + { + setPhoneVibrator(true, activity); + } + + emuVibrators = new SparseArray<>(); + for (int i = 0; i < 8; i++) + { + StringSetting deviceName = + (StringSetting) activity.getSettings().getSection(Settings.SECTION_BINDINGS) + .getSetting(SettingsFile.KEY_EMU_RUMBLE + i); + if (deviceName != null && !deviceName.getValue().isEmpty()) + { + for (int id : InputDevice.getDeviceIds()) + { + InputDevice device = InputDevice.getDevice(id); + if (deviceName.getValue().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; + } + } + + public 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); + } + } + } +} diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 2113fef188d7..effe8492e42f 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -40,6 +40,7 @@ Input Binding Press or move an input to bind it to %1$s. + Press or move any input to set rumble. Buttons @@ -291,6 +292,9 @@ General Controllers + + Device rumble not found + You need to allow write access to external storage for the emulator to work Loading Settings... Change Disc