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.
This commit is contained in:
zackhow 2018-10-01 19:36:00 -04:00
parent dd922660c9
commit 3499a416e7
12 changed files with 339 additions and 98 deletions

View File

@ -7,15 +7,11 @@
package org.dolphinemu.dolphinemu; package org.dolphinemu.dolphinemu;
import android.app.AlertDialog; 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 android.view.Surface;
import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.utils.Log; import org.dolphinemu.dolphinemu.utils.Log;
import org.dolphinemu.dolphinemu.utils.Rumble;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -245,22 +241,7 @@ public final class NativeLibrary
return; return;
} }
if (PreferenceManager.getDefaultSharedPreferences(emulationActivity) Rumble.checkRumble(padID, state);
.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);
}
}
}
} }
public static native String GetUserSetting(String gameID, String Section, String Key); public static native String GetUserSetting(String gameID, String Section, String Key);

View File

@ -50,6 +50,7 @@ import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.Rumble;
import org.dolphinemu.dolphinemu.utils.TvUtil; import org.dolphinemu.dolphinemu.utils.TvUtil;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -277,6 +278,7 @@ public final class EmulationActivity extends AppCompatActivity
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Rumble.initRumble(this);
setContentView(R.layout.activity_emulation); setContentView(R.layout.activity_emulation);
@ -361,6 +363,13 @@ public final class EmulationActivity extends AppCompatActivity
mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION); mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION);
} }
@Override
protected void onStop()
{
super.onStop();
Rumble.clear();
}
@Override @Override
public void onBackPressed() public void onBackPressed()
{ {
@ -693,6 +702,7 @@ public final class EmulationActivity extends AppCompatActivity
final SharedPreferences.Editor editor = mPreferences.edit(); final SharedPreferences.Editor editor = mPreferences.edit();
editor.putBoolean("phoneRumble", state); editor.putBoolean("phoneRumble", state);
editor.apply(); editor.apply();
Rumble.setPhoneVibrator(state, this);
} }
@ -950,6 +960,11 @@ public final class EmulationActivity extends AppCompatActivity
.commit(); .commit();
} }
public boolean deviceHasTouchScreen()
{
return mDeviceHasTouchScreen;
}
public String getSelectedTitle() public String getSelectedTitle()
{ {
return mSelectedTitle; return mSelectedTitle;

View File

@ -3,14 +3,18 @@ package org.dolphinemu.dolphinemu.dialogs;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Vibrator;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; 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.ControllerMappingHelper;
import org.dolphinemu.dolphinemu.utils.Log; import org.dolphinemu.dolphinemu.utils.Log;
import org.dolphinemu.dolphinemu.utils.Rumble;
import org.dolphinemu.dolphinemu.utils.TvUtil; import org.dolphinemu.dolphinemu.utils.TvUtil;
import java.util.ArrayList; import java.util.ArrayList;
@ -49,7 +53,8 @@ public final class MotionAlertDialog extends AlertDialog
case KeyEvent.ACTION_UP: case KeyEvent.ACTION_UP:
if (!ControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode)) 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. // Even if we ignore the key, we still consume it. Thus return true regardless.
return true; return true;
@ -63,14 +68,12 @@ public final class MotionAlertDialog extends AlertDialog
public boolean onKeyLongPress(int keyCode, KeyEvent event) public boolean onKeyLongPress(int keyCode, KeyEvent event)
{ {
// Option to clear by long back is only needed on the TV interface // 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) setting.clearValue();
{ dismiss();
clearBinding();
return true; return true;
} }
}
return super.onKeyLongPress(keyCode, event); return super.onKeyLongPress(keyCode, event);
} }
@ -162,69 +165,10 @@ public final class MotionAlertDialog extends AlertDialog
if (numMovedAxis == 1) if (numMovedAxis == 1)
{ {
mWaitingForEvent = false; mWaitingForEvent = false;
saveMotionInput(input, lastMovedRange, lastMovedDir); setting.onMotionInput(input, lastMovedRange, lastMovedDir);
dismiss();
} }
} }
return true; 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();
}
} }

View File

@ -1,9 +1,15 @@
package org.dolphinemu.dolphinemu.features.settings.model.view; 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.Setting;
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; 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) public InputBindingSetting(String key, String section, int titleId, Setting setting)
{ {
@ -21,6 +27,37 @@ public final class InputBindingSetting extends SettingsItem
return setting.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, * 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. * initializes a new one and returns it, so it can be added to the Hashmap.
@ -28,8 +65,15 @@ public final class InputBindingSetting extends SettingsItem
* @param bind The input that will be bound * @param bind The input that will be bound
* @return null if overwritten successfully; otherwise, a newly created StringSetting. * @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) if (getSetting() == null)
{ {
StringSetting setting = new StringSetting(getKey(), getSection(), bind); StringSetting setting = new StringSetting(getKey(), getSection(), bind);
@ -44,6 +88,11 @@ public final class InputBindingSetting extends SettingsItem
} }
} }
public void clearValue()
{
setValue("", "");
}
@Override @Override
public int getType() public int getType()
{ {

View File

@ -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;
}
}

View File

@ -19,6 +19,7 @@ public abstract class SettingsItem
public static final int TYPE_SUBMENU = 4; public static final int TYPE_SUBMENU = 4;
public static final int TYPE_INPUT_BINDING = 5; public static final int TYPE_INPUT_BINDING = 5;
public static final int TYPE_STRING_SINGLE_CHOICE = 6; public static final int TYPE_STRING_SINGLE_CHOICE = 6;
public static final int TYPE_RUMBLE_BINDING = 7;
private String mKey; private String mKey;
private String mSection; private String mSection;

View File

@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; 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.CheckBoxSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; 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.SettingsItem;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting;
@ -29,6 +30,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.CheckBoxSettingViewHolder; 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.HeaderViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputBindingSettingViewHolder; 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.SettingViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder;
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SliderViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SliderViewHolder;
@ -90,6 +92,10 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new InputBindingSettingViewHolder(view, this, mContext); 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: default:
Log.error("[SettingsAdapter] Invalid view type: " + viewType); Log.error("[SettingsAdapter] Invalid view type: " + viewType);
return null; return null;
@ -216,19 +222,17 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
{ {
final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item); final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item);
dialog.setTitle(R.string.input_binding); 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()))); mContext.getString(item.getNameId())));
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this); dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
(dialogInterface, i) -> (dialogInterface, i) ->
{ {
item.setValue(""); SharedPreferences preferences =
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(mContext); PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = sharedPreferences.edit(); item.clearValue();
editor.remove(item.getKey());
editor.apply();
}); });
dialog.setOnDismissListener(dialog1 -> dialog.setOnDismissListener(dialog1 ->
{ {

View File

@ -14,6 +14,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.CheckBoxSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.HeaderSetting; 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.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.SettingsItem;
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting;
@ -632,6 +633,8 @@ public final class SettingsFragmentPresenter
bindingsSection.getSetting(SettingsFile.KEY_GCBIND_DPAD_LEFT + gcPadNumber); bindingsSection.getSetting(SettingsFile.KEY_GCBIND_DPAD_LEFT + gcPadNumber);
Setting bindDPadRight = Setting bindDPadRight =
bindingsSection.getSetting(SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber); 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 HeaderSetting(null, null, R.string.generic_buttons, 0));
sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_A + gcPadNumber, sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_A + gcPadNumber,
@ -682,6 +685,11 @@ public final class SettingsFragmentPresenter
Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft)); Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft));
sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber, sl.add(new InputBindingSetting(SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber,
Settings.SECTION_BINDINGS, R.string.generic_right, bindDPadRight)); 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 else // Adapter
{ {
@ -761,6 +769,8 @@ public final class SettingsFragmentPresenter
bindingsSection.getSetting(SettingsFile.KEY_WIIBIND_DPAD_LEFT + wiimoteNumber); bindingsSection.getSetting(SettingsFile.KEY_WIIBIND_DPAD_LEFT + wiimoteNumber);
Setting bindDPadRight = Setting bindDPadRight =
bindingsSection.getSetting(SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber); 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, sl.add(new SingleChoiceSetting(SettingsFile.KEY_WIIMOTE_EXTENSION,
Settings.SECTION_WIIMOTE + (wiimoteNumber - 3), R.string.wiimote_extensions, Settings.SECTION_WIIMOTE + (wiimoteNumber - 3), R.string.wiimote_extensions,
@ -843,6 +853,11 @@ public final class SettingsFragmentPresenter
Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft)); Settings.SECTION_BINDINGS, R.string.generic_left, bindDPadLeft));
sl.add(new InputBindingSetting(SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber, sl.add(new InputBindingSetting(SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber,
Settings.SECTION_BINDINGS, R.string.generic_right, bindDPadRight)); 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<SettingsItem> sl, int wiimoteNumber, private void addExtensionTypeSettings(ArrayList<SettingsItem> sl, int wiimoteNumber,

View File

@ -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());
}
}

View File

@ -115,6 +115,8 @@ public final class SettingsFile
public static final String KEY_GCADAPTER_RUMBLE = "AdapterRumble"; public static final String KEY_GCADAPTER_RUMBLE = "AdapterRumble";
public static final String KEY_GCADAPTER_BONGOS = "SimulateKonga"; 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_TYPE = "Source";
public static final String KEY_WIIMOTE_EXTENSION = "Extension"; public static final String KEY_WIIMOTE_EXTENSION = "Extension";

View File

@ -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<Vibrator> 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);
}
}
}
}

View File

@ -40,6 +40,7 @@
<string name="input_binding">Input Binding</string> <string name="input_binding">Input Binding</string>
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string> <string name="input_binding_description">Press or move an input to bind it to %1$s.</string>
<string name="input_rumble_description">Press or move any input to set rumble.</string>
<!-- Generic buttons (Shared with lots of stuff) --> <!-- Generic buttons (Shared with lots of stuff) -->
<string name="generic_buttons">Buttons</string> <string name="generic_buttons">Buttons</string>
@ -291,6 +292,9 @@
<string name="header_wiimote_general">General</string> <string name="header_wiimote_general">General</string>
<string name="header_controllers">Controllers</string> <string name="header_controllers">Controllers</string>
<!-- Rumble -->
<string name="rumble_not_found">Device rumble not found</string>
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string> <string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
<string name="load_settings">Loading Settings...</string> <string name="load_settings">Loading Settings...</string>
<string name="emulation_change_disc">Change Disc</string> <string name="emulation_change_disc">Change Disc</string>