From c1c88eb41c462cba69baeab996b23e13e35d0170 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 8 Nov 2020 15:18:37 +1000 Subject: [PATCH] Android: Implement vibrate-on-press and dualshock vibration --- .../app/src/cpp/android_host_interface.cpp | 67 ++++++ android/app/src/cpp/android_host_interface.h | 7 + android/app/src/main/AndroidManifest.xml | 1 + .../duckstation/AndroidHostInterface.java | 6 + .../duckstation/EmulationActivity.java | 36 ++- .../github/stenzek/duckstation/FileUtil.java | 5 - .../stenzek/duckstation/GameListEntry.java | 4 +- .../TouchscreenControllerButtonView.java | 10 + .../TouchscreenControllerView.java | 5 +- android/app/src/main/res/values/arrays.xml | 170 +++++++------- .../src/main/res/xml/advanced_preferences.xml | 145 ++++++------ .../src/main/res/xml/audio_preferences.xml | 88 ++++---- .../main/res/xml/controllers_preferences.xml | 102 +++++---- .../src/main/res/xml/display_preferences.xml | 109 +++++---- .../main/res/xml/enhancements_preferences.xml | 211 +++++++++--------- .../src/main/res/xml/general_preferences.xml | 147 ++++++------ 16 files changed, 616 insertions(+), 497 deletions(-) diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index df2e0f0ff..16ad84361 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -6,6 +6,7 @@ #include "common/log.h" #include "common/string.h" #include "common/string_util.h" +#include "common/timer.h" #include "common/timestamp.h" #include "core/bios.h" #include "core/cheats.h" @@ -39,6 +40,7 @@ static jmethodID s_EmulationActivity_method_reportMessage; static jmethodID s_EmulationActivity_method_onEmulationStarted; static jmethodID s_EmulationActivity_method_onEmulationStopped; static jmethodID s_EmulationActivity_method_onGameTitleChanged; +static jmethodID s_EmulationActivity_method_setVibration; static jclass s_PatchCode_class; static jmethodID s_PatchCode_constructor; @@ -185,6 +187,8 @@ void AndroidHostInterface::LoadAndConvertSettings() &g_settings.cpu_overclock_denominator); g_settings.cpu_overclock_enable = (overclock_percent != 100); g_settings.UpdateOverclockActive(); + + m_vibration_enabled = m_settings_interface.GetBoolValue("Controller1", "Vibration", false); } void AndroidHostInterface::UpdateInputMap() @@ -353,8 +357,13 @@ void AndroidHostInterface::EmulationThreadLoop() // simulate the system if not paused if (System::IsRunning()) + { System::RunFrame(); + if (m_vibration_enabled) + UpdateVibration(); + } + // rendering { DrawImGuiWindows(); @@ -432,10 +441,21 @@ std::unique_ptr AndroidHostInterface::CreateAudioStream(AudioBacken return CommonHostInterface::CreateAudioStream(backend); } +void AndroidHostInterface::OnSystemPaused(bool paused) +{ + CommonHostInterface::OnSystemPaused(paused); + + if (m_vibration_enabled) + SetVibration(false); +} + void AndroidHostInterface::OnSystemDestroyed() { CommonHostInterface::OnSystemDestroyed(); ClearOSDMessages(); + + if (m_vibration_enabled) + SetVibration(false); } void AndroidHostInterface::OnRunningGameChanged() @@ -612,6 +632,51 @@ bool AndroidHostInterface::ImportPatchCodesFromString(const std::string& str) return true; } +void AndroidHostInterface::SetVibration(bool enabled) +{ + const u64 current_time = Common::Timer::GetValue(); + if (Common::Timer::ConvertValueToSeconds(current_time - m_last_vibration_update_time) < 0.1f && + m_last_vibration_state == enabled) + { + return; + } + + m_last_vibration_state = enabled; + m_last_vibration_update_time = current_time; + + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + if (m_emulation_activity_object) { + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_setVibration, + static_cast(enabled)); + } +} + +void AndroidHostInterface::UpdateVibration() +{ + static constexpr float THRESHOLD = 0.5f; + + bool vibration_state = false; + + for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) + { + Controller* controller = System::GetController(i); + if (!controller) + continue; + + const u32 motors = controller->GetVibrationMotorCount(); + for (u32 j = 0; j < motors; j++) + { + if (controller->GetVibrationMotorStrength(j) >= THRESHOLD) + { + vibration_state = true; + break; + } + } + } + + SetVibration(vibration_state); +} + extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV); @@ -653,6 +718,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr || (s_EmulationActivity_method_onGameTitleChanged = env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr || + (s_EmulationActivity_method_setVibration = + env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == nullptr || (s_PatchCode_constructor = env->GetMethodID(s_PatchCode_class, "", "(ILjava/lang/String;Z)V")) == nullptr) { Log_ErrorPrint("AndroidHostInterface lookups failed"); diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index 6ad39d297..4ec0fbe99 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -65,6 +65,7 @@ protected: void ReleaseHostDisplay() override; std::unique_ptr CreateAudioStream(AudioBackend backend) override; + void OnSystemPaused(bool paused) override; void OnSystemDestroyed() override; void OnRunningGameChanged() override; @@ -77,6 +78,8 @@ private: void DestroyImGuiContext(); void LoadAndConvertSettings(); + void SetVibration(bool enabled); + void UpdateVibration(); jobject m_java_object = {}; jobject m_emulation_activity_object = {}; @@ -92,6 +95,10 @@ private: std::thread m_emulation_thread; std::atomic_bool m_emulation_thread_stop_request{false}; + + u64 m_last_vibration_update_time = 0; + bool m_last_vibration_state = false; + bool m_vibration_enabled = false; }; namespace AndroidHelpers { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 29fc9bf6e..214b089c7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.github.stenzek.duckstation"> + { + if (mVibratorService == null) + return; + + if (enabled) + mVibratorService.vibrate(1000); + else + mVibratorService.cancel(); + }); + } } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java index 15ae7c1f1..26e7233b4 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java @@ -9,19 +9,14 @@ import android.net.Uri; import android.os.Build; import android.os.storage.StorageManager; import android.provider.DocumentsContract; -import android.widget.Toast; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.Charset; diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java index 9c29de547..0f3d1d1d5 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java @@ -73,7 +73,9 @@ public class GameListEntry { return mTitle; } - public String getFileTitle() { return mFileTitle; } + public String getFileTitle() { + return mFileTitle; + } public String getModifiedTime() { return mModifiedTime; diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java index f55db677f..ec273f921 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java @@ -5,6 +5,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; import android.view.View; /** @@ -14,6 +15,7 @@ public class TouchscreenControllerButtonView extends View { private Drawable mUnpressedDrawable; private Drawable mPressedDrawable; private boolean mPressed = false; + private boolean mHapticFeedback = false; private int mControllerIndex = -1; private int mButtonCode = -1; @@ -81,6 +83,10 @@ public class TouchscreenControllerButtonView extends View { mPressed = pressed; invalidate(); updateControllerState(); + + if (mHapticFeedback) { + performHapticFeedback(pressed ? HapticFeedbackConstants.VIRTUAL_KEY : HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); + } } public void setButtonCode(int controllerIndex, int code) { @@ -88,6 +94,10 @@ public class TouchscreenControllerButtonView extends View { mButtonCode = code; } + public void setHapticFeedback(boolean enabled) { + mHapticFeedback = enabled; + } + private void updateControllerState() { if (mButtonCode >= 0) AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java index ff5f061d1..c62774fd7 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java @@ -20,6 +20,7 @@ public class TouchscreenControllerView extends FrameLayout { private View mMainView; private ArrayList mButtonViews = new ArrayList<>(); private ArrayList mAxisViews = new ArrayList<>(); + private boolean mHapticFeedback; public TouchscreenControllerView(Context context) { super(context); @@ -33,9 +34,10 @@ public class TouchscreenControllerView extends FrameLayout { super(context, attrs, defStyle); } - public void init(int controllerIndex, String controllerType, String viewType) { + public void init(int controllerIndex, String controllerType, String viewType, boolean hapticFeedback) { mControllerIndex = controllerIndex; mControllerType = controllerType; + mHapticFeedback = hapticFeedback; mButtonViews.clear(); mAxisViews.clear(); @@ -99,6 +101,7 @@ public class TouchscreenControllerView extends FrameLayout { if (code >= 0) { buttonView.setButtonCode(mControllerIndex, code); + buttonView.setHapticFeedback(mHapticFeedback); mButtonViews.add(buttonView); } else { Log.e("TouchscreenController", String.format("Unknown button name '%s' " + diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 101b9a7d9..5392263ab 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -318,89 +318,89 @@ 1000% [600 FPS (NTSC) / 500 FPS (PAL)] - 0.0 - 0.1 - 0.2 - 0.3 - 0.4 - 0.5 - 0.6 - 0.7 - 0.8 - 0.9 - 1.0 - 1.25 - 1.5 - 1.75 - 2.0 - 2.5 - 3.0 - 3.5 - 4.0 - 4.5 - 5.0 - 6.0 - 7.0 - 8.0 - 9.0 - 10.0 - - - 25% (8MHz) - 50% (16MHz) - 75% (24MHz) - 100% (33MHz, Default) - 125% (41MHz) - 150% (49MHz) - 175% (57MHz) - 200% (66MHz) - 225% (74MHz) - 250% (82MHz) - 275% (90MHz) - 300% (99MHz) - 350% (115MHz) - 400% (132MHz) - 450% (148MHz) - 500% (165MHz) - 500% (165MHz) - 600% (198MHz) - 700% (231MHz) - 800% (264MHz) - 900% (297MHz) - 1000% (330MHz) - - - 25 - 50 - 75 - 100 - 125 - 150 - 175 - 200 - 225 - 250 - 275 - 300 - 350 - 400 - 450 - 500 - 500 - 600 - 700 - 800 - 900 - 1000 - - - Use Device Setting - Portrait - Landscape - - - unspecified - portrait - landscape - + 0.0 + 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 0.6 + 0.7 + 0.8 + 0.9 + 1.0 + 1.25 + 1.5 + 1.75 + 2.0 + 2.5 + 3.0 + 3.5 + 4.0 + 4.5 + 5.0 + 6.0 + 7.0 + 8.0 + 9.0 + 10.0 + + + 25% (8MHz) + 50% (16MHz) + 75% (24MHz) + 100% (33MHz, Default) + 125% (41MHz) + 150% (49MHz) + 175% (57MHz) + 200% (66MHz) + 225% (74MHz) + 250% (82MHz) + 275% (90MHz) + 300% (99MHz) + 350% (115MHz) + 400% (132MHz) + 450% (148MHz) + 500% (165MHz) + 500% (165MHz) + 600% (198MHz) + 700% (231MHz) + 800% (264MHz) + 900% (297MHz) + 1000% (330MHz) + + + 25 + 50 + 75 + 100 + 125 + 150 + 175 + 200 + 225 + 250 + 275 + 300 + 350 + 400 + 450 + 500 + 500 + 600 + 700 + 800 + 900 + 1000 + + + Use Device Setting + Portrait + Landscape + + + unspecified + portrait + landscape + diff --git a/android/app/src/main/res/xml/advanced_preferences.xml b/android/app/src/main/res/xml/advanced_preferences.xml index da5d699c8..6953f5c72 100644 --- a/android/app/src/main/res/xml/advanced_preferences.xml +++ b/android/app/src/main/res/xml/advanced_preferences.xml @@ -14,78 +14,77 @@ ~ limitations under the License. --> - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/android/app/src/main/res/xml/audio_preferences.xml b/android/app/src/main/res/xml/audio_preferences.xml index e85f42155..1ea8ddba5 100644 --- a/android/app/src/main/res/xml/audio_preferences.xml +++ b/android/app/src/main/res/xml/audio_preferences.xml @@ -17,49 +17,49 @@ - - - - - - + + + + + + diff --git a/android/app/src/main/res/xml/controllers_preferences.xml b/android/app/src/main/res/xml/controllers_preferences.xml index 51bb7b2ad..565ce5ce2 100644 --- a/android/app/src/main/res/xml/controllers_preferences.xml +++ b/android/app/src/main/res/xml/controllers_preferences.xml @@ -14,52 +14,64 @@ ~ limitations under the License. --> - - - - - - + - + app:key="Controller1/Type" + app:title="Controller Type" + app:entries="@array/settings_controller_type_entries" + app:entryValues="@array/settings_controller_type_values" + app:defaultValue="DigitalController" + app:useSimpleSummaryProvider="true" + app:iconSpaceReserved="false" /> + + + + + + + + + diff --git a/android/app/src/main/res/xml/display_preferences.xml b/android/app/src/main/res/xml/display_preferences.xml index f5428bb23..d7535e5e4 100644 --- a/android/app/src/main/res/xml/display_preferences.xml +++ b/android/app/src/main/res/xml/display_preferences.xml @@ -14,64 +14,63 @@ ~ limitations under the License. --> - + - + - + - + - - - - - - + + + + + + diff --git a/android/app/src/main/res/xml/enhancements_preferences.xml b/android/app/src/main/res/xml/enhancements_preferences.xml index b28c6b34f..6e1c527e3 100644 --- a/android/app/src/main/res/xml/enhancements_preferences.xml +++ b/android/app/src/main/res/xml/enhancements_preferences.xml @@ -14,122 +14,121 @@ ~ limitations under the License. --> - - - - - - + + + + + + - + - + - + - + - + - + - + - + - + - + - + diff --git a/android/app/src/main/res/xml/general_preferences.xml b/android/app/src/main/res/xml/general_preferences.xml index 2fb1ec76a..2441497f6 100644 --- a/android/app/src/main/res/xml/general_preferences.xml +++ b/android/app/src/main/res/xml/general_preferences.xml @@ -14,82 +14,81 @@ ~ limitations under the License. --> - + - - - - - - - - + + + + + + + + - + - +