diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 603b14077..000000000 --- a/android/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx diff --git a/android/.idea/.name b/android/.idea/.name deleted file mode 100644 index eea1d2e85..000000000 --- a/android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -DuckStation \ No newline at end of file diff --git a/android/.idea/codeStyles/Project.xml b/android/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41ae2..000000000 --- a/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/android/.idea/codeStyles/codeStyleConfig.xml b/android/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a179..000000000 --- a/android/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml deleted file mode 100644 index fb7f4a8a4..000000000 --- a/android/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml deleted file mode 100644 index 9bba60dad..000000000 --- a/android/.idea/gradle.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android/.idea/jarRepositories.xml b/android/.idea/jarRepositories.xml deleted file mode 100644 index e34606ccd..000000000 --- a/android/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml deleted file mode 100644 index 860da66a5..000000000 --- a/android/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/android/.idea/runConfigurations.xml b/android/.idea/runConfigurations.xml deleted file mode 100644 index e497da999..000000000 --- a/android/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml deleted file mode 100644 index 6c0b86358..000000000 --- a/android/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/app/.gitignore b/android/app/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/android/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 38ec9c8df..000000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,90 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 29 - buildToolsVersion "30.0.2" - defaultConfig { - applicationId "com.github.stenzek.duckstation" - minSdkVersion 23 - targetSdkVersion 29 - versionCode(getBuildVersionCode()) - versionName "${getVersion()}" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - - ndk { - debugSymbolLevel "FULL" - } - } - } - - externalNativeBuild { - cmake { - path "../../CMakeLists.txt" - version "3.10.2" - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - defaultConfig { - externalNativeBuild { - cmake { - arguments "-DCMAKE_BUILD_TYPE=Release" - abiFilters "arm64-v8a", "armeabi-v7a", "x86_64" - } - } - } - sourceSets { - main.assets.srcDirs += "../../data" - } - - // Blocked on R21 until CMake is updated to 3.19 or later. - ndkVersion '21.4.7075529' -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.preference:preference:1.1.1' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation "androidx.viewpager2:viewpager2:1.0.0" - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' -} - -// Adapted from Dolphin. - -def getVersion() { - def versionNumber = '0.0-unknown' - - try { - versionNumber = 'git describe --tags --exclude latest --exclude preview'.execute([], project.rootDir).text - .trim() - .replaceAll(/(-0)?-[^-]+$/, "") - } catch (Exception e) { - logger.error('Cannot find git, defaulting to dummy version number') - } - - return versionNumber -} - - -def getBuildVersionCode() { - try { - def versionNumber = 'git rev-list --first-parent --count HEAD'.execute([], project.rootDir).text - .trim() - return Integer.valueOf(versionNumber); - } catch (Exception e) { - logger.error('Cannot find git, defaulting to dummy version number') - } - - return 1; -} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/android/app/src/androidTest/java/com/github/stenzek/duckstation/ExampleInstrumentedTest.java b/android/app/src/androidTest/java/com/github/stenzek/duckstation/ExampleInstrumentedTest.java deleted file mode 100644 index 71cc603ed..000000000 --- a/android/app/src/androidTest/java/com/github/stenzek/duckstation/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.github.stenzek.duckstation", appContext.getPackageName()); - } -} diff --git a/android/app/src/cpp/CMakeLists.txt b/android/app/src/cpp/CMakeLists.txt deleted file mode 100644 index 2e3fb11ed..000000000 --- a/android/app/src/cpp/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -set(SRCS - android_controller_interface.cpp - android_controller_interface.h - android_host_interface.cpp - android_host_interface.h - android_http_downloader.cpp - android_http_downloader.h - android_progress_callback.cpp - android_progress_callback.h - android_settings_interface.cpp - android_settings_interface.h -) - -add_library(duckstation-native SHARED ${SRCS}) -target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui) - -find_package(OpenSLES) -if(OPENSLES_FOUND) - message("Enabling OpenSL ES audio stream") - target_sources(duckstation-native PRIVATE - opensles_audio_stream.cpp - opensles_audio_stream.h) - target_link_libraries(duckstation-native PRIVATE OpenSLES::OpenSLES) - target_compile_definitions(duckstation-native PRIVATE "-DUSE_OPENSLES=1") -endif() diff --git a/android/app/src/cpp/android_controller_interface.cpp b/android/app/src/cpp/android_controller_interface.cpp deleted file mode 100644 index 7fea10ecd..000000000 --- a/android/app/src/cpp/android_controller_interface.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "android_controller_interface.h" -#include "android_host_interface.h" -#include "common/assert.h" -#include "common/file_system.h" -#include "common/log.h" -#include "core/controller.h" -#include "core/host_interface.h" -#include "core/system.h" -#include -Log_SetChannel(AndroidControllerInterface); - -AndroidControllerInterface::AndroidControllerInterface() = default; - -AndroidControllerInterface::~AndroidControllerInterface() = default; - -ControllerInterface::Backend AndroidControllerInterface::GetBackend() const -{ - return ControllerInterface::Backend::Android; -} - -bool AndroidControllerInterface::Initialize(CommonHostInterface* host_interface) -{ - if (!ControllerInterface::Initialize(host_interface)) - return false; - - return true; -} - -void AndroidControllerInterface::Shutdown() -{ - ControllerInterface::Shutdown(); -} - -void AndroidControllerInterface::PollEvents() {} - -void AndroidControllerInterface::ClearBindings() -{ - std::unique_lock lock(m_controllers_mutex); - for (ControllerData& cd : m_controllers) - { - cd.axis_mapping.clear(); - cd.button_mapping.clear(); - cd.axis_button_mapping.clear(); - cd.button_axis_mapping.clear(); - } -} - -std::optional AndroidControllerInterface::GetControllerIndex(const std::string_view& device) -{ - std::unique_lock lock(m_controllers_mutex); - for (u32 i = 0; i < static_cast(m_device_names.size()); i++) - { - if (device == m_device_names[i]) - return static_cast(i); - } - - return std::nullopt; -} - -bool AndroidControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, - AxisCallback callback) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - m_controllers[controller_index].axis_mapping[axis_number][axis_side] = std::move(callback); - Log_DevPrintf("Bound controller %d axis %d side %u", controller_index, axis_number, static_cast(axis_side)); - return true; -} - -bool AndroidControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - m_controllers[controller_index].button_mapping[button_number] = std::move(callback); - Log_DevPrintf("Bound controller %d button %d", controller_index, button_number); - return true; -} - -bool AndroidControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction, - ButtonCallback callback) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - m_controllers[controller_index].axis_button_mapping[axis_number][BoolToUInt8(direction)] = std::move(callback); - Log_DevPrintf("Bound controller %d axis %d to button", controller_index, axis_number); - return true; -} - -bool AndroidControllerInterface::BindControllerHatToButton(int controller_index, int hat_number, - std::string_view hat_position, ButtonCallback callback) -{ - return false; -} - -bool AndroidControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, - AxisCallback callback) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - m_controllers[controller_index].button_axis_mapping[button_number] = std::move(callback); - Log_DevPrintf("Bound controller %d button %d to axis", controller_index, button_number); - return true; -} - -void AndroidControllerInterface::SetDeviceNames(std::vector device_names) -{ - std::unique_lock lock(m_controllers_mutex); - m_device_names = std::move(device_names); - m_controllers.resize(m_device_names.size()); - - for (u32 i = 0; i < static_cast(m_device_names.size()); i++) - Log_DevPrintf("Controller %u: %s", i, m_device_names[i].c_str()); -} - -void AndroidControllerInterface::SetDeviceRumble(u32 index, bool has_vibrator) -{ - std::unique_lock lock(m_controllers_mutex); - if (index >= m_controllers.size()) - return; - - m_controllers[index].has_rumble = has_vibrator; -} - -void AndroidControllerInterface::HandleAxisEvent(u32 index, u32 axis, float value) -{ - std::unique_lock lock(m_controllers_mutex); - if (index >= m_controllers.size()) - return; - - Log_DevPrintf("controller %u axis %u %f", index, axis, value); - if (DoEventHook(Hook::Type::Axis, index, axis, value)) - return; - - const ControllerData& cd = m_controllers[index]; - const auto am_iter = cd.axis_mapping.find(axis); - if (am_iter != cd.axis_mapping.end()) - { - const AxisCallback& cb = am_iter->second[AxisSide::Full]; - if (cb) - { - cb(value); - return; - } - } - - // set the other direction to false so large movements don't leave the opposite on - const bool outside_deadzone = (std::abs(value) >= cd.deadzone); - const bool positive = (value >= 0.0f); - const auto bm_iter = cd.axis_button_mapping.find(axis); - if (bm_iter != cd.axis_button_mapping.end()) - { - const ButtonCallback& other_button_cb = bm_iter->second[BoolToUInt8(!positive)]; - const ButtonCallback& button_cb = bm_iter->second[BoolToUInt8(positive)]; - if (button_cb) - { - button_cb(outside_deadzone); - if (other_button_cb) - other_button_cb(false); - return; - } - else if (other_button_cb) - { - other_button_cb(false); - return; - } - } -} - -void AndroidControllerInterface::HandleButtonEvent(u32 index, u32 button, bool pressed) -{ - Log_DevPrintf("controller %u button %u %s", index, button, pressed ? "pressed" : "released"); - - std::unique_lock lock(m_controllers_mutex); - if (index >= m_controllers.size()) - return; - - if (DoEventHook(Hook::Type::Button, index, button, pressed ? 1.0f : 0.0f)) - return; - - const ControllerData& cd = m_controllers[index]; - const auto button_iter = cd.button_mapping.find(button); - if (button_iter != cd.button_mapping.end() && button_iter->second) - { - button_iter->second(pressed); - return; - } - - const auto axis_iter = cd.button_axis_mapping.find(button); - if (axis_iter != cd.button_axis_mapping.end() && axis_iter->second) - { - axis_iter->second(pressed ? 1.0f : -1.0f); - return; - } - - Log_DevPrintf("controller %u button %u has no binding", index, button); -} - -bool AndroidControllerInterface::HasButtonBinding(u32 index, u32 button) -{ - std::unique_lock lock(m_controllers_mutex); - if (index >= m_controllers.size()) - return false; - - const ControllerData& cd = m_controllers[index]; - return (cd.button_mapping.find(button) != cd.button_mapping.end() || - cd.button_axis_mapping.find(button) != cd.button_axis_mapping.end()); -} - -u32 AndroidControllerInterface::GetControllerRumbleMotorCount(int controller_index) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - return m_controllers[static_cast(controller_index)].has_rumble ? NUM_RUMBLE_MOTORS : 0; -} - -void AndroidControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, - u32 num_motors) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return; - - const float small_motor = strengths[0]; - const float large_motor = strengths[1]; - static_cast(m_host_interface) - ->SetControllerVibration(static_cast(controller_index), small_motor, large_motor); -} - -bool AndroidControllerInterface::SetControllerDeadzone(int controller_index, float size /* = 0.25f */) -{ - std::unique_lock lock(m_controllers_mutex); - if (static_cast(controller_index) >= m_controllers.size()) - return false; - - m_controllers[static_cast(controller_index)].deadzone = std::clamp(std::abs(size), 0.01f, 0.99f); - Log_InfoPrintf("Controller %d deadzone size set to %f", controller_index, - m_controllers[static_cast(controller_index)].deadzone); - return true; -} diff --git a/android/app/src/cpp/android_controller_interface.h b/android/app/src/cpp/android_controller_interface.h deleted file mode 100644 index a22b81f50..000000000 --- a/android/app/src/cpp/android_controller_interface.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once -#include "core/types.h" -#include "frontend-common/controller_interface.h" -#include -#include -#include -#include -#include - -class AndroidControllerInterface final : public ControllerInterface -{ -public: - AndroidControllerInterface(); - ~AndroidControllerInterface() override; - - ALWAYS_INLINE u32 GetControllerCount() const { return static_cast(m_controllers.size()); } - - Backend GetBackend() const override; - bool Initialize(CommonHostInterface* host_interface) override; - void Shutdown() override; - - // Removes all bindings. Call before setting new bindings. - void ClearBindings() override; - - // Binding to events. If a binding for this axis/button already exists, returns false. - std::optional GetControllerIndex(const std::string_view& device) override; - bool BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, AxisCallback callback) override; - bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; - bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, - ButtonCallback callback) override; - bool BindControllerHatToButton(int controller_index, int hat_number, std::string_view hat_position, - ButtonCallback callback) override; - bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; - - // Changing rumble strength. - u32 GetControllerRumbleMotorCount(int controller_index) override; - void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override; - - // Set deadzone that will be applied on axis-to-button mappings - bool SetControllerDeadzone(int controller_index, float size = 0.25f) override; - - void PollEvents() override; - - void SetDeviceNames(std::vector device_names); - void SetDeviceRumble(u32 index, bool has_vibrator); - void HandleAxisEvent(u32 index, u32 axis, float value); - void HandleButtonEvent(u32 index, u32 button, bool pressed); - bool HasButtonBinding(u32 index, u32 button); - -private: - enum : u32 - { - NUM_RUMBLE_MOTORS = 2 - }; - - struct ControllerData - { - float deadzone = 0.25f; - - std::map> axis_mapping; - std::map button_mapping; - std::map> axis_button_mapping; - std::map button_axis_mapping; - bool has_rumble = false; - }; - - std::vector m_device_names; - std::vector m_controllers; - std::mutex m_controllers_mutex; - - std::mutex m_event_intercept_mutex; - Hook::Callback m_event_intercept_callback; -}; diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp deleted file mode 100644 index ea4ec3dd0..000000000 --- a/android/app/src/cpp/android_host_interface.cpp +++ /dev/null @@ -1,2198 +0,0 @@ -#include "android_host_interface.h" -#include "android_controller_interface.h" -#include "android_progress_callback.h" -#include "common/assert.h" -#include "common/audio_stream.h" -#include "common/file_system.h" -#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" -#include "core/controller.h" -#include "core/gpu.h" -#include "core/host_display.h" -#include "core/memory_card_image.h" -#include "core/system.h" -#include "frontend-common/cheevos.h" -#include "frontend-common/game_list.h" -#include "frontend-common/imgui_fullscreen.h" -#include "frontend-common/imgui_styles.h" -#include "frontend-common/opengl_host_display.h" -#include "frontend-common/vulkan_host_display.h" -#include "scmversion/scmversion.h" -#include -#include -#include -#include -#include -Log_SetChannel(AndroidHostInterface); - -#ifdef USE_OPENSLES -#include "opensles_audio_stream.h" -#endif - -static JavaVM* s_jvm; -static jclass s_String_class; -static jclass s_AndroidHostInterface_class; -static jmethodID s_AndroidHostInterface_constructor; -static jfieldID s_AndroidHostInterface_field_mNativePointer; -static jfieldID s_AndroidHostInterface_field_mEmulationActivity; -static jmethodID s_AndroidHostInterface_method_reportError; -static jmethodID s_AndroidHostInterface_method_reportMessage; -static jmethodID s_AndroidHostInterface_method_openAssetStream; -static jclass s_EmulationActivity_class; -static jmethodID s_EmulationActivity_method_reportError; -static jmethodID s_EmulationActivity_method_onEmulationStarted; -static jmethodID s_EmulationActivity_method_onEmulationStopped; -static jmethodID s_EmulationActivity_method_onRunningGameChanged; -static jmethodID s_EmulationActivity_method_setVibration; -static jmethodID s_EmulationActivity_method_getRefreshRate; -static jmethodID s_EmulationActivity_method_openPauseMenu; -static jmethodID s_EmulationActivity_method_getInputDeviceNames; -static jmethodID s_EmulationActivity_method_hasInputDeviceVibration; -static jmethodID s_EmulationActivity_method_setInputDeviceVibration; -static jclass s_PatchCode_class; -static jmethodID s_PatchCode_constructor; -static jclass s_GameListEntry_class; -static jmethodID s_GameListEntry_constructor; -static jclass s_SaveStateInfo_class; -static jmethodID s_SaveStateInfo_constructor; -static jclass s_Achievement_class; -static jmethodID s_Achievement_constructor; -static jclass s_MemoryCardFileInfo_class; -static jmethodID s_MemoryCardFileInfo_constructor; - -namespace AndroidHelpers { -JavaVM* GetJavaVM() -{ - return s_jvm; -} - -// helper for retrieving the current per-thread jni environment -JNIEnv* GetJNIEnv() -{ - JNIEnv* env; - if (s_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) - return nullptr; - else - return env; -} - -AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj) -{ - return reinterpret_cast( - static_cast(env->GetLongField(obj, s_AndroidHostInterface_field_mNativePointer))); -} - -std::string JStringToString(JNIEnv* env, jstring str) -{ - if (str == nullptr) - return {}; - - jsize length = env->GetStringUTFLength(str); - if (length == 0) - return {}; - - const char* data = env->GetStringUTFChars(str, nullptr); - Assert(data != nullptr); - - std::string ret(data, length); - env->ReleaseStringUTFChars(str, data); - - return ret; -} - -jclass GetStringClass() -{ - return s_String_class; -} - -std::unique_ptr ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size /* = 65536*/) -{ - std::unique_ptr bs = std::make_unique(nullptr, 0); - u32 position = 0; - - jclass cls = env->GetObjectClass(obj); - jmethodID read_method = env->GetMethodID(cls, "read", "([B)I"); - Assert(read_method); - - jbyteArray temp = env->NewByteArray(chunk_size); - for (;;) - { - int bytes_read = env->CallIntMethod(obj, read_method, temp); - if (bytes_read <= 0) - break; - - if ((position + static_cast(bytes_read)) > bs->GetMemorySize()) - { - const u32 new_size = std::max(bs->GetMemorySize() * 2, position + static_cast(bytes_read)); - bs->ResizeMemory(new_size); - } - - env->GetByteArrayRegion(temp, 0, bytes_read, reinterpret_cast(bs->GetMemoryPointer() + position)); - position += static_cast(bytes_read); - } - - bs->Resize(position); - env->DeleteLocalRef(temp); - env->DeleteLocalRef(cls); - return bs; -} - -std::vector ByteArrayToVector(JNIEnv* env, jbyteArray obj) -{ - std::vector ret; - const jsize size = obj ? env->GetArrayLength(obj) : 0; - if (size > 0) - { - jbyte* data = env->GetByteArrayElements(obj, nullptr); - ret.resize(static_cast(size)); - std::memcpy(ret.data(), data, ret.size()); - env->ReleaseByteArrayElements(obj, data, 0); - } - - return ret; -} - -jbyteArray NewByteArray(JNIEnv* env, const void* data, size_t size) -{ - if (!data || size == 0) - return nullptr; - - jbyteArray obj = env->NewByteArray(static_cast(size)); - jbyte* obj_data = env->GetByteArrayElements(obj, nullptr); - std::memcpy(obj_data, data, static_cast(static_cast(size))); - env->ReleaseByteArrayElements(obj, obj_data, 0); - return obj; -} - -jbyteArray VectorToByteArray(JNIEnv* env, const std::vector& data) -{ - if (data.empty()) - return nullptr; - - return NewByteArray(env, data.data(), data.size()); -} - -jobjectArray CreateObjectArray(JNIEnv* env, jclass object_class, const jobject* objects, size_t num_objects, - bool release_refs /* = false*/) -{ - if (!objects || num_objects == 0) - return nullptr; - - jobjectArray arr = env->NewObjectArray(static_cast(num_objects), object_class, nullptr); - for (jsize i = 0; i < static_cast(num_objects); i++) - { - env->SetObjectArrayElement(arr, i, objects[i]); - if (release_refs && objects[i]) - env->DeleteLocalRef(objects[i]); - } - - return arr; -} -} // namespace AndroidHelpers - -AndroidHostInterface::AndroidHostInterface(jobject java_object, jobject context_object, std::string user_directory) - : m_java_object(java_object) -{ - m_user_directory = std::move(user_directory); - m_settings_interface = std::make_unique(context_object); -} - -AndroidHostInterface::~AndroidHostInterface() -{ - ImGui::DestroyContext(); - AndroidHelpers::GetJNIEnv()->DeleteGlobalRef(m_java_object); -} - -bool AndroidHostInterface::Initialize() -{ - if (!CommonHostInterface::Initialize()) - return false; - - return true; -} - -void AndroidHostInterface::Shutdown() -{ - HostInterface::Shutdown(); -} - -const char* AndroidHostInterface::GetFrontendName() const -{ - return "DuckStation Android"; -} - -void AndroidHostInterface::RequestExit() -{ - ReportError("Ignoring RequestExit()"); -} - -void AndroidHostInterface::ReportError(const char* message) -{ - CommonHostInterface::ReportError(message); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - jstring message_jstr = env->NewStringUTF(message); - if (m_emulation_activity_object) - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportError, message_jstr); - else - env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportError, message_jstr); - env->DeleteLocalRef(message_jstr); -} - -void AndroidHostInterface::ReportMessage(const char* message) -{ - CommonHostInterface::ReportMessage(message); - - if (IsOnEmulationThread()) - { - // The toasts are not visible when the emulation activity is running anyway. - AddOSDMessage(message, 5.0f); - } - else - { - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder message_jstr(env, env->NewStringUTF(message)); - env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportMessage, message_jstr.Get()); - } -} - -std::unique_ptr AndroidHostInterface::OpenPackageFile(const char* path, u32 flags) -{ - Log_DevPrintf("OpenPackageFile(%s, %x)", path, flags); - if (flags & (BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE)) - return {}; - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - jobject stream = - env->CallObjectMethod(m_java_object, s_AndroidHostInterface_method_openAssetStream, env->NewStringUTF(path)); - if (!stream) - { - Log_ErrorPrintf("Package file '%s' not found", path); - return {}; - } - - std::unique_ptr ret(AndroidHelpers::ReadInputStreamToMemory(env, stream, 65536)); - env->DeleteLocalRef(stream); - return ret; -} - -void AndroidHostInterface::RegisterHotkeys() -{ - RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "General")), StaticString("OpenPauseMenu"), - StaticString(TRANSLATABLE("Hotkeys", "Open Pause Menu")), [this](bool pressed) { - if (pressed) - { - AndroidHelpers::GetJNIEnv()->CallVoidMethod(m_emulation_activity_object, - s_EmulationActivity_method_openPauseMenu); - } - }); - - CommonHostInterface::RegisterHotkeys(); -} - -float AndroidHostInterface::GetRefreshRate() const -{ - if (!m_emulation_activity_object) - return 0.0f; - - const float value = AndroidHelpers::GetJNIEnv()->CallFloatMethod(m_emulation_activity_object, - s_EmulationActivity_method_getRefreshRate); - return (value > 0.0f) ? value : 0.0f; -} - -float AndroidHostInterface::GetSurfaceScale(int width, int height) const -{ - if (width <= 0 || height <= 0) - return 1.0f; - - // TODO: Really need a better way of determining this. - return (width > height) ? (static_cast(width) / 1280.0f) : (static_cast(height) / 1280.0f); -} - -void AndroidHostInterface::SetUserDirectory() -{ - // Already set in constructor. - Assert(!m_user_directory.empty()); -} - -void AndroidHostInterface::LoadSettings(SettingsInterface& si) -{ - const GPURenderer old_renderer = g_settings.gpu_renderer; - CommonHostInterface::LoadSettings(si); - - const std::string msaa_str = si.GetStringValue("GPU", "MSAA", "1"); - g_settings.gpu_multisamples = std::max(StringUtil::FromChars(msaa_str).value_or(1), 1); - g_settings.gpu_per_sample_shading = StringUtil::EndsWith(msaa_str, "-ssaa"); - - // turn percentage into fraction for overclock - const u32 overclock_percent = static_cast(std::max(si.GetIntValue("CPU", "Overclock", 100), 1)); - Settings::CPUOverclockPercentToFraction(overclock_percent, &g_settings.cpu_overclock_numerator, - &g_settings.cpu_overclock_denominator); - g_settings.cpu_overclock_enable = (overclock_percent != 100); - g_settings.UpdateOverclockActive(); - - m_vibration_enabled = si.GetBoolValue("Controller1", "Vibration", false); - - // Defer renderer changes, the app really doesn't like it. - if (System::IsValid() && g_settings.gpu_renderer != old_renderer) - { - AddFormattedOSDMessage(5.0f, - TranslateString("OSDMessage", "Change to %s GPU renderer will take effect on restart."), - Settings::GetRendererName(g_settings.gpu_renderer)); - g_settings.gpu_renderer = old_renderer; - } -} - -void AndroidHostInterface::UpdateInputMap(SettingsInterface& si) -{ - if (m_emulation_activity_object) - { - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - DebugAssert(env); - - std::vector device_names; - - jobjectArray const java_names = reinterpret_cast( - env->CallObjectMethod(m_emulation_activity_object, s_EmulationActivity_method_getInputDeviceNames)); - if (java_names) - { - const u32 count = static_cast(env->GetArrayLength(java_names)); - for (u32 i = 0; i < count; i++) - { - device_names.push_back( - AndroidHelpers::JStringToString(env, reinterpret_cast(env->GetObjectArrayElement(java_names, i)))); - } - - env->DeleteLocalRef(java_names); - } - - if (m_controller_interface) - { - AndroidControllerInterface* ci = static_cast(m_controller_interface.get()); - if (ci) - { - ci->SetDeviceNames(std::move(device_names)); - for (u32 i = 0; i < ci->GetControllerCount(); i++) - { - const bool has_vibration = env->CallBooleanMethod( - m_emulation_activity_object, s_EmulationActivity_method_hasInputDeviceVibration, static_cast(i)); - ci->SetDeviceRumble(i, has_vibration); - } - } - } - } - - CommonHostInterface::UpdateInputMap(si); -} - -bool AndroidHostInterface::IsEmulationThreadPaused() const -{ - return System::IsValid() && System::IsPaused(); -} - -void AndroidHostInterface::PauseEmulationThread(bool paused) -{ - Assert(IsEmulationThreadRunning()); - RunOnEmulationThread([this, paused]() { PauseSystem(paused); }); -} - -void AndroidHostInterface::StopEmulationThreadLoop() -{ - if (!IsEmulationThreadRunning()) - return; - - std::unique_lock lock(m_mutex); - m_emulation_thread_stop_request.store(true); - m_sleep_cv.notify_one(); -} - -bool AndroidHostInterface::IsOnEmulationThread() const -{ - return std::this_thread::get_id() == m_emulation_thread_id; -} - -void AndroidHostInterface::RunOnEmulationThread(std::function function, bool blocking) -{ - if (!IsEmulationThreadRunning()) - { - function(); - return; - } - - m_mutex.lock(); - m_callback_queue.push_back(std::move(function)); - m_callbacks_outstanding.store(true); - m_sleep_cv.notify_one(); - - if (blocking) - { - // TODO: Don't spin - for (;;) - { - if (!m_callbacks_outstanding.load()) - break; - - m_mutex.unlock(); - m_mutex.lock(); - } - } - - m_mutex.unlock(); -} - -void AndroidHostInterface::RunLater(std::function func) -{ - std::unique_lock lock(m_mutex); - m_callback_queue.push_back(std::move(func)); - m_callbacks_outstanding.store(true); -} - -void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulation_activity, - SystemBootParameters boot_params, bool resume_state) -{ - if (!m_surface) - { - Log_ErrorPrint("Emulation thread started without surface set."); - env->CallVoidMethod(emulation_activity, s_EmulationActivity_method_onEmulationStopped); - return; - } - - emulation_activity = env->NewGlobalRef(emulation_activity); - Assert(emulation_activity != nullptr); - - { - std::unique_lock lock(m_mutex); - m_emulation_thread_running.store(true); - m_emulation_activity_object = emulation_activity; - m_emulation_thread_id = std::this_thread::get_id(); - env->SetObjectField(m_java_object, s_AndroidHostInterface_field_mEmulationActivity, emulation_activity); - } - - ApplySettings(true); - - // Boot system. - bool boot_result = false; - if (resume_state && boot_params.filename.empty()) - boot_result = ResumeSystemFromMostRecentState(); - else if (resume_state && CanResumeSystemFromFile(boot_params.filename.c_str())) - boot_result = ResumeSystemFromState(boot_params.filename.c_str(), true); - else - boot_result = BootSystem(boot_params); - - if (boot_result) - { - // System is ready to go. - EmulationThreadLoop(env); - PowerOffSystem(ShouldSaveResumeState()); - } - - // Drain any callbacks so we don't leave things in a screwed-up state for next boot. - { - std::unique_lock lock(m_mutex); - while (!m_callback_queue.empty()) - { - auto callback = std::move(m_callback_queue.front()); - m_callback_queue.pop_front(); - lock.unlock(); - callback(); - lock.lock(); - } - env->SetObjectField(m_java_object, s_AndroidHostInterface_field_mEmulationActivity, nullptr); - m_emulation_thread_running.store(false); - m_emulation_thread_id = {}; - m_emulation_activity_object = {}; - m_callbacks_outstanding.store(false); - } - - env->CallVoidMethod(emulation_activity, s_EmulationActivity_method_onEmulationStopped); - env->DeleteGlobalRef(emulation_activity); -} - -void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) -{ - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); - - for (;;) - { - // run any events - { - std::unique_lock lock(m_mutex); - for (;;) - { - if (!m_callback_queue.empty()) - { - do - { - auto callback = std::move(m_callback_queue.front()); - m_callback_queue.pop_front(); - lock.unlock(); - callback(); - lock.lock(); - } while (!m_callback_queue.empty()); - m_callbacks_outstanding.store(false); - } - - if (m_emulation_thread_stop_request.load()) - { - m_emulation_thread_stop_request.store(false); - return; - } - - if (System::IsPaused()) - { - // paused, wait for us to resume - m_sleep_cv.wait(lock); - } - else - { - // done with callbacks, run the frame - break; - } - } - } - - // we don't do a full PollAndUpdate() here - if (Cheevos::IsActive()) - Cheevos::Update(); - - // simulate the system if not paused - if (System::IsRunning()) - { - if (m_throttler_enabled) - System::RunFrames(); - else - System::RunFrame(); - - UpdateControllerMetaState(); - if (m_vibration_enabled) - UpdateVibration(); - } - - // rendering - { - ImGui::NewFrame(); - DrawImGuiWindows(); - - m_display->Render(); - ImGui::EndFrame(); - - if (System::IsRunning()) - { - System::UpdatePerformanceCounters(); - - if (m_throttler_enabled) - System::Throttle(); - } - } - } -} - -bool AndroidHostInterface::AcquireHostDisplay() -{ - WindowInfo wi; - wi.type = WindowInfo::Type::Android; - wi.window_handle = m_surface; - wi.surface_width = ANativeWindow_getWidth(m_surface); - wi.surface_height = ANativeWindow_getHeight(m_surface); - wi.surface_refresh_rate = GetRefreshRate(); - wi.surface_scale = GetSurfaceScale(wi.surface_width, wi.surface_height); - - switch (g_settings.gpu_renderer) - { - case GPURenderer::HardwareVulkan: - m_display = std::make_unique(); - break; - - case GPURenderer::HardwareOpenGL: - default: - m_display = std::make_unique(); - break; - } - - if (!m_display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device, g_settings.gpu_threaded_presentation) || - !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, - g_settings.gpu_threaded_presentation)) - { - m_display->DestroyRenderDevice(); - m_display.reset(); - return false; - } - - // The alignment was set prior to booting. - m_display->SetDisplayAlignment(m_display_alignment); - - if (!CreateHostDisplayResources()) - { - ReportError("Failed to create host display resources"); - ReleaseHostDisplay(); - return false; - } - - return true; -} - -void AndroidHostInterface::ReleaseHostDisplay() -{ - ReleaseHostDisplayResources(); - if (m_display) - { - m_display->DestroyRenderDevice(); - m_display.reset(); - } -} - -std::unique_ptr AndroidHostInterface::CreateAudioStream(AudioBackend backend) -{ -#ifdef USE_OPENSLES - if (backend == AudioBackend::OpenSLES) - return OpenSLESAudioStream::Create(); -#endif - - return CommonHostInterface::CreateAudioStream(backend); -} - -void AndroidHostInterface::UpdateControllerInterface() -{ - if (m_controller_interface) - { - m_controller_interface->Shutdown(); - m_controller_interface.reset(); - } - - m_controller_interface = std::make_unique(); - if (!m_controller_interface || !m_controller_interface->Initialize(this)) - { - Log_WarningPrintf("Failed to initialize controller interface, bindings are not possible."); - if (m_controller_interface) - { - m_controller_interface->Shutdown(); - m_controller_interface.reset(); - } - } -} - -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(const std::string& path, CDImage* image, const std::string& game_code, - const std::string& game_title) -{ - CommonHostInterface::OnRunningGameChanged(path, image, game_code, game_title); - - if (m_emulation_activity_object) - { - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - - jstring path_string = env->NewStringUTF(path.c_str()); - jstring code_string = env->NewStringUTF(game_code.c_str()); - jstring title_string = env->NewStringUTF(game_title.c_str()); - - const GameListEntry* game_list_entry = m_game_list->GetEntryForPath(path.c_str()); - std::string cover_path_str; - if (game_list_entry) - cover_path_str = m_game_list->GetCoverImagePathForEntry(game_list_entry); - else - cover_path_str = m_game_list->GetCoverImagePath(path, game_code, game_title); - - jstring cover_path = nullptr; - if (!cover_path_str.empty()) - cover_path = env->NewStringUTF(cover_path_str.c_str()); - - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onRunningGameChanged, path_string, - code_string, title_string, cover_path); - - if (cover_path) - env->DeleteLocalRef(cover_path); - env->DeleteLocalRef(title_string); - env->DeleteLocalRef(code_string); - env->DeleteLocalRef(path_string); - } -} - -void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height) -{ - Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height); - if (m_surface == surface) - { - if (m_display && (width != m_display->GetWindowWidth() || height != m_display->GetWindowHeight())) - { - m_display->ResizeRenderWindow(width, height); - OnHostDisplayResized(); - } - - return; - } - - m_surface = surface; - - if (m_display) - { - WindowInfo wi; - wi.type = surface ? WindowInfo::Type::Android : WindowInfo::Type::Surfaceless; - wi.window_handle = surface; - wi.surface_width = width; - wi.surface_height = height; - wi.surface_refresh_rate = GetRefreshRate(); - wi.surface_scale = GetSurfaceScale(width, height); - - const bool surface_valid = m_display->ChangeRenderWindow(wi) && surface; - if (surface_valid) - OnHostDisplayResized(); - - if (surface_valid && System::GetState() == System::State::Paused) - PauseSystem(false); - else if (!surface_valid && System::IsRunning()) - PauseSystem(true); - } -} - -void AndroidHostInterface::SetDisplayAlignment(HostDisplay::Alignment alignment) -{ - m_display_alignment = alignment; - if (m_display) - m_display->SetDisplayAlignment(alignment); -} - -void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code, bool pressed) -{ - if (!IsEmulationThreadRunning()) - return; - - RunOnEmulationThread( - [index, button_code, pressed]() { - Controller* controller = System::GetController(index); - if (!controller) - return; - - controller->SetButtonState(button_code, pressed); - }, - false); -} - -void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, float value) -{ - if (!IsEmulationThreadRunning()) - return; - - RunOnEmulationThread( - [index, button_code, value]() { - Controller* controller = System::GetController(index); - if (!controller) - return; - - controller->SetAxisState(button_code, value); - }, - false); -} - -void AndroidHostInterface::HandleControllerButtonEvent(u32 controller_index, u32 button_index, bool pressed) -{ - if (!IsEmulationThreadRunning()) - return; - - RunOnEmulationThread([this, controller_index, button_index, pressed]() { - AndroidControllerInterface* ci = static_cast(m_controller_interface.get()); - if (ci) - ci->HandleButtonEvent(controller_index, button_index, pressed); - }); -} - -void AndroidHostInterface::HandleControllerAxisEvent(u32 controller_index, u32 axis_index, float value) -{ - if (!IsEmulationThreadRunning()) - return; - - RunOnEmulationThread([this, controller_index, axis_index, value]() { - AndroidControllerInterface* ci = static_cast(m_controller_interface.get()); - if (ci) - ci->HandleAxisEvent(controller_index, axis_index, value); - }); -} - -bool AndroidHostInterface::HasControllerButtonBinding(u32 controller_index, u32 button) -{ - AndroidControllerInterface* ci = static_cast(m_controller_interface.get()); - if (!ci) - return false; - - return ci->HasButtonBinding(controller_index, button); -} - -void AndroidHostInterface::SetControllerVibration(u32 controller_index, float small_motor, float large_motor) -{ - if (!m_emulation_activity_object) - return; - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - DebugAssert(env); - - env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_setInputDeviceVibration, - static_cast(controller_index), static_cast(small_motor), - static_cast(large_motor)); -} - -void AndroidHostInterface::SetFastForwardEnabled(bool enabled) -{ - m_fast_forward_enabled = enabled; - UpdateSpeedLimiterState(); -} - -void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database, - ProgressCallback* progress_callback) -{ - m_game_list->SetSearchDirectoriesFromSettings(*m_settings_interface); - m_game_list->Refresh(invalidate_cache, invalidate_database, progress_callback); -} - -bool AndroidHostInterface::ImportPatchCodesFromString(const std::string& str) -{ - CheatList* cl = new CheatList(); - if (!cl->LoadFromString(str, CheatList::Format::Autodetect) || cl->GetCodeCount() == 0) - return false; - - RunOnEmulationThread([this, cl]() { - u32 imported_count; - if (!System::HasCheatList()) - { - imported_count = cl->GetCodeCount(); - System::SetCheatList(std::unique_ptr(cl)); - } - else - { - const u32 old_count = System::GetCheatList()->GetCodeCount(); - System::GetCheatList()->MergeList(*cl); - imported_count = System::GetCheatList()->GetCodeCount() - old_count; - delete cl; - } - - AddFormattedOSDMessage(20.0f, "Imported %u patch codes.", imported_count); - CommonHostInterface::SaveCheatList(); - }); - - 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); -} - -jobjectArray AndroidHostInterface::GetInputProfileNames(JNIEnv* env) const -{ - const InputProfileList profile_list(GetInputProfileList()); - if (profile_list.empty()) - return nullptr; - - jobjectArray name_array = env->NewObjectArray(static_cast(profile_list.size()), s_String_class, nullptr); - u32 name_array_index = 0; - Assert(name_array != nullptr); - for (const InputProfileEntry& e : profile_list) - { - jstring axis_name_jstr = env->NewStringUTF(e.name.c_str()); - env->SetObjectArrayElement(name_array, name_array_index++, axis_name_jstr); - env->DeleteLocalRef(axis_name_jstr); - } - - return name_array; -} - -extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV); - s_jvm = vm; - - // Create global reference so it doesn't get cleaned up. - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - jclass string_class, host_interface_class, patch_code_class, game_list_entry_class, save_state_info_class, - achievement_class, memory_card_file_info_class; - if ((string_class = env->FindClass("java/lang/String")) == nullptr || - (s_String_class = static_cast(env->NewGlobalRef(string_class))) == nullptr || - (host_interface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) == nullptr || - (s_AndroidHostInterface_class = static_cast(env->NewGlobalRef(host_interface_class))) == nullptr || - (patch_code_class = env->FindClass("com/github/stenzek/duckstation/PatchCode")) == nullptr || - (s_PatchCode_class = static_cast(env->NewGlobalRef(patch_code_class))) == nullptr || - (game_list_entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry")) == nullptr || - (s_GameListEntry_class = static_cast(env->NewGlobalRef(game_list_entry_class))) == nullptr || - (save_state_info_class = env->FindClass("com/github/stenzek/duckstation/SaveStateInfo")) == nullptr || - (s_SaveStateInfo_class = static_cast(env->NewGlobalRef(save_state_info_class))) == nullptr || - (achievement_class = env->FindClass("com/github/stenzek/duckstation/Achievement")) == nullptr || - (s_Achievement_class = static_cast(env->NewGlobalRef(achievement_class))) == nullptr || - (memory_card_file_info_class = env->FindClass("com/github/stenzek/duckstation/MemoryCardFileInfo")) == nullptr || - (s_MemoryCardFileInfo_class = static_cast(env->NewGlobalRef(memory_card_file_info_class))) == nullptr) - { - Log_ErrorPrint("AndroidHostInterface class lookup failed"); - return -1; - } - - env->DeleteLocalRef(string_class); - env->DeleteLocalRef(host_interface_class); - env->DeleteLocalRef(patch_code_class); - env->DeleteLocalRef(game_list_entry_class); - env->DeleteLocalRef(achievement_class); - env->DeleteLocalRef(memory_card_file_info_class); - - jclass emulation_activity_class; - if ((s_AndroidHostInterface_constructor = - env->GetMethodID(s_AndroidHostInterface_class, "", - "(Landroid/content/Context;Lcom/github/stenzek/duckstation/FileHelper;)V")) == nullptr || - (s_AndroidHostInterface_field_mNativePointer = - env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr || - (s_AndroidHostInterface_field_mEmulationActivity = - env->GetFieldID(s_AndroidHostInterface_class, "mEmulationActivity", - "Lcom/github/stenzek/duckstation/EmulationActivity;")) == nullptr || - (s_AndroidHostInterface_method_reportError = - env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || - (s_AndroidHostInterface_method_reportMessage = - env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || - (s_AndroidHostInterface_method_openAssetStream = env->GetMethodID( - s_AndroidHostInterface_class, "openAssetStream", "(Ljava/lang/String;)Ljava/io/InputStream;")) == nullptr || - (emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr || - (s_EmulationActivity_class = static_cast(env->NewGlobalRef(emulation_activity_class))) == nullptr || - (s_EmulationActivity_method_reportError = - env->GetMethodID(s_EmulationActivity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || - (s_EmulationActivity_method_onEmulationStarted = - env->GetMethodID(s_EmulationActivity_class, "onEmulationStarted", "()V")) == nullptr || - (s_EmulationActivity_method_onEmulationStopped = - env->GetMethodID(s_EmulationActivity_class, "onEmulationStopped", "()V")) == nullptr || - (s_EmulationActivity_method_onRunningGameChanged = - env->GetMethodID(s_EmulationActivity_class, "onRunningGameChanged", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) == nullptr || - (s_EmulationActivity_method_setVibration = env->GetMethodID(emulation_activity_class, "setVibration", "(Z)V")) == - nullptr || - (s_EmulationActivity_method_getRefreshRate = - env->GetMethodID(emulation_activity_class, "getRefreshRate", "()F")) == nullptr || - (s_EmulationActivity_method_openPauseMenu = env->GetMethodID(emulation_activity_class, "openPauseMenu", "()V")) == - nullptr || - (s_EmulationActivity_method_getInputDeviceNames = - env->GetMethodID(s_EmulationActivity_class, "getInputDeviceNames", "()[Ljava/lang/String;")) == nullptr || - (s_EmulationActivity_method_hasInputDeviceVibration = - env->GetMethodID(s_EmulationActivity_class, "hasInputDeviceVibration", "(I)Z")) == nullptr || - (s_EmulationActivity_method_setInputDeviceVibration = - env->GetMethodID(s_EmulationActivity_class, "setInputDeviceVibration", "(IFF)V")) == nullptr || - (s_PatchCode_constructor = - env->GetMethodID(s_PatchCode_class, "", "(ILjava/lang/String;Ljava/lang/String;Z)V")) == nullptr || - (s_GameListEntry_constructor = - env->GetMethodID(s_GameListEntry_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V")) == nullptr || - (s_SaveStateInfo_constructor = env->GetMethodID( - s_SaveStateInfo_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZII[B)V")) == - nullptr || - (s_Achievement_constructor = env->GetMethodID( - s_Achievement_class, "", - "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V")) == nullptr || - (s_MemoryCardFileInfo_constructor = env->GetMethodID(s_MemoryCardFileInfo_class, "", - "(Ljava/lang/String;Ljava/lang/String;III[[B)V")) == nullptr) - { - Log_ErrorPrint("AndroidHostInterface lookups failed"); - return -1; - } - - env->DeleteLocalRef(emulation_activity_class); - - return JNI_VERSION_1_6; -} - -#define DEFINE_JNI_METHOD(return_type, name) \ - extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env) - -#define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \ - extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__) - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_getScmVersion, jobject unused) -{ - return env->NewStringUTF(g_scm_tag_str); -} - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_getFullScmVersion, jobject unused) -{ - return env->NewStringUTF(SmallString::FromFormat("DuckStation for Android %s (%s)\nBuilt %s %s", g_scm_tag_str, - g_scm_branch_str, __DATE__, __TIME__)); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setThreadAffinity, jobject unused, jintArray cores) -{ - // https://github.com/googlearchive/android-audio-high-performance/blob/c232c21bf35d3bfea16537b781c526b8abdcc3cf/SimpleSynth/app/src/main/cpp/audio_player.cc - int length = env->GetArrayLength(cores); - int* p_cores = env->GetIntArrayElements(cores, nullptr); - - pid_t current_thread_id = gettid(); - cpu_set_t cpu_set; - CPU_ZERO(&cpu_set); - for (int i = 0; i < length; i++) - { - Log_InfoPrintf("Binding to CPU %d", p_cores[i]); - CPU_SET(p_cores[i], &cpu_set); - } - - int result = sched_setaffinity(current_thread_id, sizeof(cpu_set_t), &cpu_set); - if (result != 0) - Log_InfoPrintf("Thread affinity set."); - else - Log_ErrorPrintf("Error setting thread affinity: %d", result); - - env->ReleaseIntArrayElements(cores, p_cores, 0); -} - -DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object, - jobject file_helper_object, jstring user_directory) -{ - Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG); - - // initialize the java side - jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object, - file_helper_object); - if (!java_obj) - { - Log_ErrorPrint("Failed to create Java AndroidHostInterface"); - return nullptr; - } - - jobject java_obj_ref = env->NewGlobalRef(java_obj); - Assert(java_obj_ref != nullptr); - - // initialize the C++ side - std::string user_directory_str = AndroidHelpers::JStringToString(env, user_directory); - AndroidHostInterface* cpp_obj = new AndroidHostInterface(java_obj_ref, context_object, std::move(user_directory_str)); - if (!cpp_obj->Initialize()) - { - // TODO: Do we need to release the original java object reference? - Log_ErrorPrint("Failed to create C++ AndroidHostInterface"); - env->DeleteGlobalRef(java_obj_ref); - return nullptr; - } - - env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer, - static_cast(reinterpret_cast(cpp_obj))); - - FileSystem::SetAndroidFileHelper(s_jvm, env, file_helper_object); - return java_obj; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, jobject obj) -{ - return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning(); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_runEmulationThread, jobject obj, jobject emulationActivity, - jstring filename, jboolean resume_state, jstring state_filename) -{ - std::string state_filename_str = AndroidHelpers::JStringToString(env, state_filename); - - SystemBootParameters boot_params; - boot_params.filename = AndroidHelpers::JStringToString(env, filename); - - AndroidHelpers::GetNativeClass(env, obj)->EmulationThreadEntryPoint(env, emulationActivity, std::move(boot_params), - resume_state); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThreadLoop, jobject obj) -{ - AndroidHelpers::GetNativeClass(env, obj)->StopEmulationThreadLoop(); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, jobject surface, jint format, jint width, - jint height) -{ - ANativeWindow* native_surface = surface ? ANativeWindow_fromSurface(env, surface) : nullptr; - if (surface && !native_surface) - Log_ErrorPrint("ANativeWindow_fromSurface() returned null"); - - if (!surface && System::GetState() == System::State::Starting) - { - // User switched away from the app while it was compiling shaders. - Log_ErrorPrintf("Surface destroyed while starting, cancelling"); - System::CancelPendingStartup(); - } - - // We should wait for the emu to finish if the surface is being destroyed or changed. - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const bool block = (!native_surface || native_surface != hi->GetSurface()); - hi->RunOnEmulationThread( - [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, - block); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setMousePosition, jobject obj, jint positionX, jint positionY) -{ - HostDisplay* display = AndroidHelpers::GetNativeClass(env, obj)->GetDisplay(); - if (!display) - return; - - // Technically a race, but shouldn't cause any issues. - display->SetMousePosition(positionX, positionY); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerButtonState, jobject obj, jint index, jint button_code, - jboolean pressed) -{ - AndroidHelpers::GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAutoFireState, jobject obj, jint controller_index, - jint autofire_index, jboolean active) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - if (!hi->IsEmulationThreadRunning()) - return; - - hi->RunOnEmulationThread([hi, controller_index, autofire_index, active]() { - hi->SetControllerAutoFireSlotState(controller_index, autofire_index, active); - }); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type, - jstring button_name) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return -1; - - std::optional code = - Controller::GetButtonCodeByName(type.value(), AndroidHelpers::JStringToString(env, button_name)); - return code.value_or(-1); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAxisState, jobject obj, jint index, jint button_code, - jfloat value) -{ - AndroidHelpers::GetNativeClass(env, obj)->SetControllerAxisState(index, button_code, value); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisCode, jobject unused, jstring controller_type, - jstring axis_name) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return -1; - - std::optional code = - Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name)); - return code.value_or(-1); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisType, jobject unused, jstring controller_type, - jstring axis_name) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return -1; - - const std::string axis_name_str(AndroidHelpers::JStringToString(env, axis_name)); - for (const auto& [name, code, type] : Controller::GetAxisNames(type.value())) - { - if (name == axis_name_str) - return static_cast(type); - } - - return -1; -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getControllerButtonNames, jobject unused, - jstring controller_type) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return nullptr; - - const Controller::ButtonList buttons(Controller::GetButtonNames(type.value())); - if (buttons.empty()) - return nullptr; - - jobjectArray name_array = env->NewObjectArray(static_cast(buttons.size()), s_String_class, nullptr); - u32 name_array_index = 0; - Assert(name_array != nullptr); - for (const auto& [button_name, button_code] : buttons) - { - jstring button_name_jstr = env->NewStringUTF(button_name.c_str()); - env->SetObjectArrayElement(name_array, name_array_index++, button_name_jstr); - env->DeleteLocalRef(button_name_jstr); - } - - return name_array; -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getControllerAxisNames, jobject unused, - jstring controller_type) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return nullptr; - - const Controller::AxisList axes(Controller::GetAxisNames(type.value())); - if (axes.empty()) - return nullptr; - - jobjectArray name_array = env->NewObjectArray(static_cast(axes.size()), s_String_class, nullptr); - u32 name_array_index = 0; - Assert(name_array != nullptr); - for (const auto& [axis_name, axis_code, axis_type] : axes) - { - jstring axis_name_jstr = env->NewStringUTF(axis_name.c_str()); - env->SetObjectArrayElement(name_array, name_array_index++, axis_name_jstr); - env->DeleteLocalRef(axis_name_jstr); - } - - return name_array; -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerVibrationMotorCount, jobject unused, - jstring controller_type) -{ - std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); - if (!type) - return 0; - - return static_cast(Controller::GetVibrationMotorCount(type.value())); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_handleControllerButtonEvent, jobject obj, jint controller_index, - jint button_index, jboolean pressed) -{ - AndroidHelpers::GetNativeClass(env, obj)->HandleControllerButtonEvent(static_cast(controller_index), - static_cast(button_index), pressed); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_handleControllerAxisEvent, jobject obj, jint controller_index, - jint axis_index, jfloat value) -{ - AndroidHelpers::GetNativeClass(env, obj)->HandleControllerAxisEvent(static_cast(controller_index), - static_cast(axis_index), value); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasControllerButtonBinding, jobject obj, jint controller_index, - jint button_index) -{ - return AndroidHelpers::GetNativeClass(env, obj)->HasControllerButtonBinding(static_cast(controller_index), - static_cast(button_index)); -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getInputProfileNames, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - return hi->GetInputProfileNames(env); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_loadInputProfile, jobject obj, jstring name) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const std::string profile_name(AndroidHelpers::JStringToString(env, name)); - if (profile_name.empty()) - return false; - - const std::string profile_path(hi->GetInputProfilePath(profile_name.c_str())); - if (profile_path.empty()) - return false; - - return hi->ApplyInputProfile(profile_path.c_str()); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_saveInputProfile, jobject obj, jstring name) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const std::string profile_name(AndroidHelpers::JStringToString(env, name)); - if (profile_name.empty()) - return false; - - const std::string profile_path(hi->GetSavePathForInputProfile(profile_name.c_str())); - if (profile_path.empty()) - return false; - - return hi->SaveInputProfile(profile_path.c_str()); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, - jboolean invalidate_database, jobject progress_callback) -{ - AndroidProgressCallback cb(env, progress_callback); - AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database, &cb); -} - -static const char* DiscRegionToString(DiscRegion region) -{ - static std::array names = {{"NTSC_J", "NTSC_U", "PAL", "Other"}}; - return names[static_cast(region)]; -} - -static jobject CreateGameListEntry(JNIEnv* env, AndroidHostInterface* hi, const GameListEntry& entry) -{ - const Timestamp modified_ts( - Timestamp::FromUnixTimestamp(static_cast(entry.last_modified_time))); - const std::string cover_path_str(hi->GetGameList()->GetCoverImagePathForEntry(&entry)); - - jstring path = env->NewStringUTF(entry.path.c_str()); - jstring code = env->NewStringUTF(entry.code.c_str()); - jstring title = env->NewStringUTF(entry.title.c_str()); - jstring region = env->NewStringUTF(DiscRegionToString(entry.region)); - jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type)); - jstring compatibility_rating = - env->NewStringUTF(GameList::EntryCompatibilityRatingToString(entry.compatibility_rating)); - jstring cover_path = (cover_path_str.empty()) ? nullptr : env->NewStringUTF(cover_path_str.c_str()); - jstring modified_time = env->NewStringUTF(modified_ts.ToString("%Y/%m/%d, %H:%M:%S")); - jlong size = entry.total_size; - - jobject entry_jobject = env->NewObject(s_GameListEntry_class, s_GameListEntry_constructor, path, code, title, size, - modified_time, region, type, compatibility_rating, cover_path); - - env->DeleteLocalRef(modified_time); - if (cover_path) - env->DeleteLocalRef(cover_path); - env->DeleteLocalRef(compatibility_rating); - env->DeleteLocalRef(type); - env->DeleteLocalRef(region); - env->DeleteLocalRef(title); - env->DeleteLocalRef(code); - env->DeleteLocalRef(path); - - return entry_jobject; -} - -DEFINE_JNI_ARGS_METHOD(jarray, AndroidHostInterface_getGameListEntries, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - jobjectArray entry_array = env->NewObjectArray(hi->GetGameList()->GetEntryCount(), s_GameListEntry_class, nullptr); - Assert(entry_array != nullptr); - - u32 counter = 0; - for (const GameListEntry& entry : hi->GetGameList()->GetEntries()) - { - jobject entry_jobject = CreateGameListEntry(env, hi, entry); - env->SetObjectArrayElement(entry_array, counter++, entry_jobject); - env->DeleteLocalRef(entry_jobject); - } - - return entry_array; -} - -DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getGameListEntry, jobject obj, jstring path) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const std::string path_str(AndroidHelpers::JStringToString(env, path)); - const GameListEntry* entry = hi->GetGameList()->GetEntryForPath(path_str.c_str()); - if (!entry) - return nullptr; - - return CreateGameListEntry(env, hi, *entry); -} - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_getGameSettingValue, jobject obj, jstring path, jstring key) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const std::string path_str(AndroidHelpers::JStringToString(env, path)); - const std::string key_str(AndroidHelpers::JStringToString(env, key)); - - const GameListEntry* entry = hi->GetGameList()->GetEntryForPath(path_str.c_str()); - if (!entry) - return nullptr; - - std::optional value = entry->settings.GetValueForKey(key_str); - if (!value.has_value()) - return nullptr; - else - return env->NewStringUTF(value->c_str()); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setGameSettingValue, jobject obj, jstring path, jstring key, - jstring value) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const std::string path_str(AndroidHelpers::JStringToString(env, path)); - const std::string key_str(AndroidHelpers::JStringToString(env, key)); - - const GameListEntry* entry = hi->GetGameList()->GetEntryForPath(path_str.c_str()); - if (!entry) - return; - - GameSettings::Entry new_entry(entry->settings); - - std::optional value_str; - if (value) - value_str = AndroidHelpers::JStringToString(env, value); - - new_entry.SetValueForKey(key_str, value_str); - hi->GetGameList()->UpdateGameSettings(path_str, entry->code, entry->title, new_entry, true); -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getHotkeyInfoList, jobject obj) -{ - jclass entry_class = env->FindClass("com/github/stenzek/duckstation/HotkeyInfo"); - Assert(entry_class != nullptr); - - jmethodID entry_constructor = - env->GetMethodID(entry_class, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - Assert(entry_constructor != nullptr); - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - const CommonHostInterface::HotkeyInfoList& hotkeys = hi->GetHotkeyInfoList(); - if (hotkeys.empty()) - return nullptr; - - jobjectArray entry_array = env->NewObjectArray(static_cast(hotkeys.size()), entry_class, nullptr); - Assert(entry_array != nullptr); - - u32 counter = 0; - for (const CommonHostInterface::HotkeyInfo& hk : hotkeys) - { - jstring category = env->NewStringUTF(hk.category.GetCharArray()); - jstring name = env->NewStringUTF(hk.name.GetCharArray()); - jstring display_name = env->NewStringUTF(hk.display_name.GetCharArray()); - - jobject entry_jobject = env->NewObject(entry_class, entry_constructor, category, name, display_name); - - env->SetObjectArrayElement(entry_array, counter++, entry_jobject); - env->DeleteLocalRef(entry_jobject); - env->DeleteLocalRef(display_name); - env->DeleteLocalRef(name); - env->DeleteLocalRef(category); - } - - return entry_array; -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - if (hi->IsEmulationThreadRunning()) - { - hi->RunOnEmulationThread([hi]() { hi->ApplySettings(false); }); - } - else - { - hi->ApplySettings(false); - } -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_updateInputMap, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - if (hi->IsEmulationThreadRunning()) - { - hi->RunOnEmulationThread([hi]() { hi->UpdateInputMap(); }); - } - else - { - hi->UpdateInputMap(); - } -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_resetSystem, jobject obj, jboolean global, jint slot) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi]() { hi->ResetSystem(); }); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_loadState, jobject obj, jboolean global, jint slot) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi, global, slot]() { hi->LoadState(global, slot); }); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveState, jobject obj, jboolean global, jint slot) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi, global, slot]() { hi->SaveState(global, slot); }); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveResumeState, jobject obj, jboolean wait_for_completion) -{ - if (!System::IsValid() || System::GetState() == System::State::Starting) - { - // This gets called when the surface is destroyed, which can happen while starting. - return; - } - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi]() { hi->SaveResumeSaveState(); }, wait_for_completion); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setDisplayAlignment, jobject obj, jint alignment) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread( - [hi, alignment]() { hi->SetDisplayAlignment(static_cast(alignment)); }, false); -} - -DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - HostDisplay* display = hi->GetDisplay(); - if (display) - return display->HasRenderSurface(); - else - return false; -} - -DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_isEmulationThreadPaused, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - return hi->IsEmulationThreadPaused(); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_pauseEmulationThread, jobject obj, jboolean paused) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->PauseEmulationThread(paused); -} - -DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getPatchCodeList, jobject obj) -{ - if (!System::IsValid()) - return nullptr; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - if (!System::HasCheatList()) - { - // Hopefully this won't deadlock... - hi->RunOnEmulationThread( - [hi]() { - if (!hi->LoadCheatListFromGameTitle()) - hi->LoadCheatListFromDatabase(); - }, - true); - } - - if (!System::HasCheatList()) - return nullptr; - - CheatList* cl = System::GetCheatList(); - const u32 count = cl->GetCodeCount(); - - jobjectArray arr = env->NewObjectArray(count, s_PatchCode_class, nullptr); - for (u32 i = 0; i < count; i++) - { - const CheatCode& cc = cl->GetCode(i); - - jstring group_str = cc.group.empty() ? nullptr : env->NewStringUTF(cc.group.c_str()); - jstring desc_str = env->NewStringUTF(cc.description.c_str()); - jobject java_cc = - env->NewObject(s_PatchCode_class, s_PatchCode_constructor, static_cast(i), group_str, desc_str, cc.enabled); - env->SetObjectArrayElement(arr, i, java_cc); - env->DeleteLocalRef(java_cc); - env->DeleteLocalRef(desc_str); - env->DeleteLocalRef(group_str); - } - - return arr; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_importPatchCodesFromString, jobject obj, jstring str) -{ - if (!System::IsValid()) - return false; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - return hi->ImportPatchCodesFromString(AndroidHelpers::JStringToString(env, str)); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setPatchCodeEnabled, jobject obj, jint index, jboolean enabled) -{ - if (!System::IsValid() || !System::HasCheatList()) - return; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([index, enabled, hi]() { hi->SetCheatCodeState(static_cast(index), enabled, true); }); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_addOSDMessage, jobject obj, jstring message, jfloat duration) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->AddOSDMessage(AndroidHelpers::JStringToString(env, message), duration); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasAnyBIOSImages, jobject obj) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - return hi->HasAnyBIOSImages(); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isFastForwardEnabled, jobject obj) -{ - return AndroidHelpers::GetNativeClass(env, obj)->IsRunningAtNonStandardSpeed(); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setFastForwardEnabled, jobject obj, jboolean enabled) -{ - if (!System::IsValid()) - return; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([enabled, hi]() { hi->SetFastForwardEnabled(enabled); }); -} - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_importBIOSImage, jobject obj, jbyteArray data) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - - const jsize len = env->GetArrayLength(data); - if (len != BIOS::BIOS_SIZE) - return nullptr; - - BIOS::Image image; - image.resize(static_cast(len)); - env->GetByteArrayRegion(data, 0, len, reinterpret_cast(image.data())); - - const BIOS::Hash hash = BIOS::GetHash(image); - const BIOS::ImageInfo* ii = BIOS::GetImageInfoForHash(hash); - - const std::string dest_path(hi->GetUserDirectoryRelativePath("bios/%s.bin", hash.ToString().c_str())); - if (FileSystem::FileExists(dest_path.c_str()) || - !FileSystem::WriteBinaryFile(dest_path.c_str(), image.data(), image.size())) - { - return nullptr; - } - - if (ii) - return env->NewStringUTF(ii->description); - else - return env->NewStringUTF(hash.ToString().c_str()); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasMediaSubImages, jobject obj) -{ - if (!System::IsValid()) - return false; - - return System::HasMediaSubImages(); -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getMediaSubImageTitles, jobject obj) -{ - if (!System::IsValid()) - return nullptr; - - const u32 count = System::GetMediaSubImageCount(); - if (count == 0) - return nullptr; - - jobjectArray arr = env->NewObjectArray(static_cast(count), s_String_class, nullptr); - for (u32 i = 0; i < count; i++) - { - jstring str = env->NewStringUTF(System::GetMediaSubImageTitle(i).c_str()); - env->SetObjectArrayElement(arr, static_cast(i), str); - env->DeleteLocalRef(str); - } - - return arr; -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getMediaSubImageIndex, jobject obj) -{ - if (!System::IsValid()) - return -1; - - return System::GetMediaSubImageIndex(); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_switchMediaSubImage, jobject obj, jint index) -{ - if (!System::IsValid() || index < 0 || static_cast(index) >= System::GetMediaSubImageCount()) - return false; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([index, hi]() { - if (System::IsValid()) - { - if (!System::SwitchMediaSubImage(static_cast(index))) - hi->AddOSDMessage("Disc switch failed. Please make sure the file exists."); - } - }); - - return true; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_setMediaFilename, jstring obj, jstring filename) -{ - if (!System::IsValid() || !filename) - return false; - - std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([filename_str, hi]() { - if (System::IsValid()) - { - if (!System::InsertMedia(filename_str.c_str())) - hi->AddOSDMessage("Disc switch failed. Please make sure the file exists and is a supported disc image."); - } - }); - - return true; -} - -static jobject CreateSaveStateInfo(JNIEnv* env, const CommonHostInterface::ExtendedSaveStateInfo& ssi) -{ - LocalRefHolder path(env, env->NewStringUTF(ssi.path.c_str())); - LocalRefHolder title(env, env->NewStringUTF(ssi.title.c_str())); - LocalRefHolder code(env, env->NewStringUTF(ssi.game_code.c_str())); - LocalRefHolder media_path(env, env->NewStringUTF(ssi.media_path.c_str())); - LocalRefHolder timestamp(env, env->NewStringUTF(Timestamp::FromUnixTimestamp(ssi.timestamp).ToString("%c"))); - LocalRefHolder screenshot_data; - if (!ssi.screenshot_data.empty()) - { - const jsize data_size = static_cast(ssi.screenshot_data.size() * sizeof(u32)); - screenshot_data = LocalRefHolder(env, env->NewByteArray(data_size)); - env->SetByteArrayRegion(screenshot_data.Get(), 0, data_size, - reinterpret_cast(ssi.screenshot_data.data())); - } - - return env->NewObject(s_SaveStateInfo_class, s_SaveStateInfo_constructor, path.Get(), title.Get(), code.Get(), - media_path.Get(), timestamp.Get(), static_cast(ssi.slot), - static_cast(ssi.global), static_cast(ssi.screenshot_width), - static_cast(ssi.screenshot_height), screenshot_data.Get()); -} - -static jobject CreateEmptySaveStateInfo(JNIEnv* env, s32 slot, bool global) -{ - return env->NewObject(s_SaveStateInfo_class, s_SaveStateInfo_constructor, nullptr, nullptr, nullptr, nullptr, nullptr, - static_cast(slot), static_cast(global), static_cast(0), - static_cast(0), nullptr); -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getSaveStateInfo, jobject obj, jboolean includeEmpty) -{ - if (!System::IsValid()) - return nullptr; - - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - std::vector infos; - - // +1 for the quick save only in android. - infos.reserve(1 + CommonHostInterface::PER_GAME_SAVE_STATE_SLOTS + CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS); - - const std::string& game_code = System::GetRunningCode(); - if (!game_code.empty()) - { - for (u32 i = 0; i <= CommonHostInterface::PER_GAME_SAVE_STATE_SLOTS; i++) - { - std::optional esi = - hi->GetExtendedSaveStateInfo(game_code.c_str(), static_cast(i)); - if (esi.has_value()) - { - jobject obj = CreateSaveStateInfo(env, esi.value()); - if (obj) - infos.push_back(obj); - } - else if (includeEmpty) - { - jobject obj = CreateEmptySaveStateInfo(env, static_cast(i), false); - if (obj) - infos.push_back(obj); - } - } - } - - for (u32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) - { - std::optional esi = - hi->GetExtendedSaveStateInfo(nullptr, static_cast(i)); - if (esi.has_value()) - { - jobject obj = CreateSaveStateInfo(env, esi.value()); - if (obj) - infos.push_back(obj); - } - else if (includeEmpty) - { - jobject obj = CreateEmptySaveStateInfo(env, static_cast(i), true); - if (obj) - infos.push_back(obj); - } - } - - if (infos.empty()) - return nullptr; - - jobjectArray ret = env->NewObjectArray(static_cast(infos.size()), s_SaveStateInfo_class, nullptr); - for (size_t i = 0; i < infos.size(); i++) - { - env->SetObjectArrayElement(ret, static_cast(i), infos[i]); - env->DeleteLocalRef(infos[i]); - } - - return ret; -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_toggleControllerAnalogMode, jobject obj) -{ - // hacky way to toggle analog mode - for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) - { - Controller* ctrl = System::GetController(i); - if (!ctrl) - continue; - - std::optional code = Controller::GetButtonCodeByName(ctrl->GetType(), "Analog"); - if (!code.has_value()) - continue; - - ctrl->SetButtonState(code.value(), true); - ctrl->SetButtonState(code.value(), false); - } -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setFullscreenUINotificationVerticalPosition, jobject obj, - jfloat position, jfloat direction) -{ - AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread( - [position, direction]() { ImGuiFullscreen::SetNotificationVerticalPosition(position, direction); }); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isCheevosActive, jobject obj) -{ - return Cheevos::IsActive(); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isCheevosChallengeModeActive, jobject obj) -{ - return Cheevos::IsChallengeModeActive(); -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, AndroidHostInterface_getCheevoList, jobject obj) -{ - if (!Cheevos::IsActive()) - return nullptr; - - std::vector cheevos; - Cheevos::EnumerateAchievements([env, &cheevos](const Cheevos::Achievement& cheevo) { - jstring title = env->NewStringUTF(cheevo.title.c_str()); - jstring description = env->NewStringUTF(cheevo.description.c_str()); - jstring locked_badge_path = - cheevo.locked_badge_path.empty() ? nullptr : env->NewStringUTF(cheevo.locked_badge_path.c_str()); - jstring unlocked_badge_path = - cheevo.unlocked_badge_path.empty() ? nullptr : env->NewStringUTF(cheevo.unlocked_badge_path.c_str()); - - jobject object = env->NewObject(s_Achievement_class, s_Achievement_constructor, static_cast(cheevo.id), title, - description, locked_badge_path, unlocked_badge_path, - static_cast(cheevo.points), static_cast(cheevo.locked)); - cheevos.push_back(object); - - if (unlocked_badge_path) - env->DeleteLocalRef(unlocked_badge_path); - if (locked_badge_path) - env->DeleteLocalRef(locked_badge_path); - env->DeleteLocalRef(description); - env->DeleteLocalRef(title); - return true; - }); - - if (cheevos.empty()) - return nullptr; - - jobjectArray ret = env->NewObjectArray(static_cast(cheevos.size()), s_Achievement_class, nullptr); - for (size_t i = 0; i < cheevos.size(); i++) - { - env->SetObjectArrayElement(ret, static_cast(i), cheevos[i]); - env->DeleteLocalRef(cheevos[i]); - } - - return ret; -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getCheevoCount, jobject obj) -{ - return Cheevos::GetAchievementCount(); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getUnlockedCheevoCount, jobject obj) -{ - return Cheevos::GetUnlockedAchiementCount(); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getCheevoPointsForGame, jobject obj) -{ - return Cheevos::GetCurrentPointsForGame(); -} - -DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getCheevoMaximumPointsForGame, jobject obj) -{ - return Cheevos::GetMaximumPointsForGame(); -} - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_getCheevoGameTitle, jobject obj) -{ - const std::string& title = Cheevos::GetGameTitle(); - return title.empty() ? nullptr : env->NewStringUTF(title.c_str()); -} - -DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_getCheevoGameIconPath, jobject obj) -{ - const std::string& path = Cheevos::GetGameIcon(); - return path.empty() ? nullptr : env->NewStringUTF(path.c_str()); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_cheevosLogin, jobject obj, jstring username, jstring password) -{ - const std::string username_str(AndroidHelpers::JStringToString(env, username)); - const std::string password_str(AndroidHelpers::JStringToString(env, password)); - return Cheevos::Login(username_str.c_str(), password_str.c_str()); -} - -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_cheevosLogout, jobject obj) -{ - return Cheevos::Logout(); -} - -static_assert(sizeof(MemoryCardImage::DataArray) == MemoryCardImage::DATA_SIZE); - -static MemoryCardImage::DataArray* GetMemoryCardData(JNIEnv* env, jbyteArray obj) -{ - if (!obj || env->GetArrayLength(obj) != MemoryCardImage::DATA_SIZE) - return nullptr; - - return reinterpret_cast(env->GetByteArrayElements(obj, nullptr)); -} - -static void ReleaseMemoryCardData(JNIEnv* env, jbyteArray obj, MemoryCardImage::DataArray* data) -{ - env->ReleaseByteArrayElements(obj, reinterpret_cast(data), 0); -} - -DEFINE_JNI_ARGS_METHOD(jboolean, MemoryCardImage_isValid, jclass clazz, jbyteArray obj) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return false; - - const bool res = MemoryCardImage::IsValid(*data); - ReleaseMemoryCardData(env, obj, data); - return res; -} - -DEFINE_JNI_ARGS_METHOD(void, MemoryCardImage_format, jclass clazz, jbyteArray obj) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return; - - MemoryCardImage::Format(data); - ReleaseMemoryCardData(env, obj, data); -} - -DEFINE_JNI_ARGS_METHOD(jint, MemoryCardImage_getFreeBlocks, jclass clazz, jbyteArray obj) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return 0; - - const u32 free_blocks = MemoryCardImage::GetFreeBlockCount(*data); - ReleaseMemoryCardData(env, obj, data); - return free_blocks; -} - -DEFINE_JNI_ARGS_METHOD(jobjectArray, MemoryCardImage_getFiles, jclass clazz, jbyteArray obj) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return nullptr; - - const std::vector files(MemoryCardImage::EnumerateFiles(*data)); - - std::vector file_objects; - file_objects.reserve(files.size()); - - jclass byteArrayClass = env->FindClass("[B"); - - for (const MemoryCardImage::FileInfo& file : files) - { - jobject filename = env->NewStringUTF(file.filename.c_str()); - jobject title = env->NewStringUTF(file.title.c_str()); - jobjectArray frames = nullptr; - if (!file.icon_frames.empty()) - { - frames = env->NewObjectArray(static_cast(file.icon_frames.size()), byteArrayClass, nullptr); - for (jsize i = 0; i < static_cast(file.icon_frames.size()); i++) - { - static constexpr jsize frame_size = MemoryCardImage::ICON_WIDTH * MemoryCardImage::ICON_HEIGHT * sizeof(u32); - jbyteArray frame_data = env->NewByteArray(frame_size); - jbyte* frame_data_ptr = env->GetByteArrayElements(frame_data, nullptr); - std::memcpy(frame_data_ptr, file.icon_frames[i].pixels, frame_size); - env->ReleaseByteArrayElements(frame_data, frame_data_ptr, 0); - env->SetObjectArrayElement(frames, i, frame_data); - env->DeleteLocalRef(frame_data); - } - } - - file_objects.push_back(env->NewObject(s_MemoryCardFileInfo_class, s_MemoryCardFileInfo_constructor, filename, title, - static_cast(file.size), static_cast(file.first_block), - static_cast(file.num_blocks), frames)); - - env->DeleteLocalRef(frames); - env->DeleteLocalRef(title); - env->DeleteLocalRef(filename); - } - - jobjectArray file_object_array = - AndroidHelpers::CreateObjectArray(env, s_MemoryCardFileInfo_class, file_objects.data(), file_objects.size(), true); - ReleaseMemoryCardData(env, obj, data); - return file_object_array; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, MemoryCardImage_hasFile, jclass clazz, jbyteArray obj, jstring filename) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return false; - - const std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - bool result = false; - if (!filename_str.empty()) - { - const std::vector files(MemoryCardImage::EnumerateFiles(*data)); - result = std::any_of(files.begin(), files.end(), - [&filename_str](const MemoryCardImage::FileInfo& fi) { return fi.filename == filename_str; }); - } - - ReleaseMemoryCardData(env, obj, data); - return result; -} - -DEFINE_JNI_ARGS_METHOD(jbyteArray, MemoryCardImage_readFile, jclass clazz, jbyteArray obj, jstring filename) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return nullptr; - - const std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - jbyteArray ret = nullptr; - if (!filename_str.empty()) - { - const std::vector files(MemoryCardImage::EnumerateFiles(*data)); - auto iter = std::find_if(files.begin(), files.end(), [&filename_str](const MemoryCardImage::FileInfo& fi) { - return fi.filename == filename_str; - }); - if (iter != files.end()) - { - std::vector file_data; - if (MemoryCardImage::ReadFile(*data, *iter, &file_data)) - ret = AndroidHelpers::VectorToByteArray(env, file_data); - } - } - - ReleaseMemoryCardData(env, obj, data); - return ret; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, MemoryCardImage_writeFile, jclass clazz, jbyteArray obj, jstring filename, - jbyteArray file_data) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return false; - - const std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - const std::vector file_data_vec(AndroidHelpers::ByteArrayToVector(env, file_data)); - bool ret = false; - if (!filename_str.empty()) - ret = MemoryCardImage::WriteFile(data, filename_str, file_data_vec); - - ReleaseMemoryCardData(env, obj, data); - return ret; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, MemoryCardImage_deleteFile, jclass clazz, jbyteArray obj, jstring filename) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return false; - - const std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - bool ret = false; - - if (!filename_str.empty()) - { - const std::vector files(MemoryCardImage::EnumerateFiles(*data)); - auto iter = std::find_if(files.begin(), files.end(), [&filename_str](const MemoryCardImage::FileInfo& fi) { - return fi.filename == filename_str; - }); - - if (iter != files.end()) - ret = MemoryCardImage::DeleteFile(data, *iter); - } - - ReleaseMemoryCardData(env, obj, data); - return ret; -} - -DEFINE_JNI_ARGS_METHOD(jboolean, MemoryCardImage_importCard, jclass clazz, jbyteArray obj, jstring filename, - jbyteArray import_data) -{ - MemoryCardImage::DataArray* data = GetMemoryCardData(env, obj); - if (!data) - return false; - - const std::string filename_str(AndroidHelpers::JStringToString(env, filename)); - std::vector import_data_vec(AndroidHelpers::ByteArrayToVector(env, import_data)); - bool ret = false; - if (!filename_str.empty() && !import_data_vec.empty()) - ret = MemoryCardImage::ImportCard(data, filename_str.c_str(), std::move(import_data_vec)); - - ReleaseMemoryCardData(env, obj, data); - return ret; -} \ No newline at end of file diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h deleted file mode 100644 index 99f91b5f2..000000000 --- a/android/app/src/cpp/android_host_interface.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once -#include "android_settings_interface.h" -#include "common/byte_stream.h" -#include "common/event.h" -#include "common/progress_callback.h" -#include "core/host_display.h" -#include "frontend-common/common_host_interface.h" -#include -#include -#include -#include -#include -#include -#include -#include - -struct ANativeWindow; - -class Controller; - -class AndroidHostInterface final : public CommonHostInterface -{ -public: - using CommonHostInterface::UpdateInputMap; - - AndroidHostInterface(jobject java_object, jobject context_object, std::string user_directory); - ~AndroidHostInterface() override; - - ALWAYS_INLINE ANativeWindow* GetSurface() const { return m_surface; } - - bool Initialize() override; - void Shutdown() override; - - const char* GetFrontendName() const override; - void RequestExit() override; - void RunLater(std::function func) override; - - void ReportError(const char* message) override; - void ReportMessage(const char* message) override; - - std::unique_ptr OpenPackageFile(const char* path, u32 flags) override; - - bool IsEmulationThreadRunning() const { return m_emulation_thread_running.load(); } - bool IsEmulationThreadPaused() const; - bool IsOnEmulationThread() const; - void RunOnEmulationThread(std::function function, bool blocking = false); - void PauseEmulationThread(bool paused); - void StopEmulationThreadLoop(); - - void EmulationThreadEntryPoint(JNIEnv* env, jobject emulation_activity, SystemBootParameters boot_params, - bool resume_state); - - void SurfaceChanged(ANativeWindow* surface, int format, int width, int height); - void SetDisplayAlignment(HostDisplay::Alignment alignment); - - void SetControllerButtonState(u32 index, s32 button_code, bool pressed); - void SetControllerAxisState(u32 index, s32 button_code, float value); - void HandleControllerButtonEvent(u32 controller_index, u32 button_index, bool pressed); - void HandleControllerAxisEvent(u32 controller_index, u32 axis_index, float value); - bool HasControllerButtonBinding(u32 controller_index, u32 button); - void SetControllerVibration(u32 controller_index, float small_motor, float large_motor); - void SetFastForwardEnabled(bool enabled); - - void RefreshGameList(bool invalidate_cache, bool invalidate_database, ProgressCallback* progress_callback); - - bool ImportPatchCodesFromString(const std::string& str); - - jobjectArray GetInputProfileNames(JNIEnv* env) const; - -protected: - void SetUserDirectory() override; - void RegisterHotkeys() override; - - bool AcquireHostDisplay() override; - void ReleaseHostDisplay() override; - std::unique_ptr CreateAudioStream(AudioBackend backend) override; - void UpdateControllerInterface() override; - - void OnSystemPaused(bool paused) override; - void OnSystemDestroyed() override; - void OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, - const std::string& game_title) override; - -private: - void EmulationThreadLoop(JNIEnv* env); - - void LoadSettings(SettingsInterface& si) override; - void UpdateInputMap(SettingsInterface& si) override; - void SetVibration(bool enabled); - void UpdateVibration(); - float GetRefreshRate() const; - float GetSurfaceScale(int width, int height) const; - - jobject m_java_object = {}; - jobject m_emulation_activity_object = {}; - - ANativeWindow* m_surface = nullptr; - - std::mutex m_mutex; - std::condition_variable m_sleep_cv; - std::deque> m_callback_queue; - std::atomic_bool m_callbacks_outstanding{false}; - - std::atomic_bool m_emulation_thread_stop_request{false}; - std::atomic_bool m_emulation_thread_running{false}; - std::thread::id m_emulation_thread_id{}; - - HostDisplay::Alignment m_display_alignment = HostDisplay::Alignment::Center; - - u64 m_last_vibration_update_time = 0; - bool m_last_vibration_state = false; - bool m_vibration_enabled = false; -}; - -namespace AndroidHelpers { - -JavaVM* GetJavaVM(); -JNIEnv* GetJNIEnv(); -AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj); -std::string JStringToString(JNIEnv* env, jstring str); -std::unique_ptr ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size = 65536); -jclass GetStringClass(); -std::vector ByteArrayToVector(JNIEnv* env, jbyteArray obj); -jbyteArray NewByteArray(JNIEnv* env, const void* data, size_t size); -jbyteArray VectorToByteArray(JNIEnv* env, const std::vector& data); -jobjectArray CreateObjectArray(JNIEnv* env, jclass object_class, const jobject* objects, size_t num_objects, - bool release_refs = false); -} // namespace AndroidHelpers - -template -class LocalRefHolder -{ -public: - LocalRefHolder() : m_env(nullptr), m_object(nullptr) {} - - LocalRefHolder(JNIEnv* env, T object) : m_env(env), m_object(object) {} - - LocalRefHolder(const LocalRefHolder&) = delete; - LocalRefHolder(LocalRefHolder&& move) : m_env(move.m_env), m_object(move.m_object) - { - move.m_env = nullptr; - move.m_object = {}; - } - - ~LocalRefHolder() - { - if (m_object) - m_env->DeleteLocalRef(m_object); - } - - operator T() const { return m_object; } - T operator*() const { return m_object; } - - LocalRefHolder& operator=(const LocalRefHolder&) = delete; - LocalRefHolder& operator=(LocalRefHolder&& move) - { - if (m_object) - m_env->DeleteLocalRef(m_object); - m_env = move.m_env; - m_object = move.m_object; - move.m_env = nullptr; - move.m_object = {}; - return *this; - } - - T Get() const { return m_object; } - -private: - JNIEnv* m_env; - T m_object; -}; diff --git a/android/app/src/cpp/android_http_downloader.cpp b/android/app/src/cpp/android_http_downloader.cpp deleted file mode 100644 index d1f462641..000000000 --- a/android/app/src/cpp/android_http_downloader.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "android_http_downloader.h" -#include "android_host_interface.h" -#include "common/assert.h" -#include "common/log.h" -#include "common/string_util.h" -#include "common/timer.h" -#include -#include -Log_SetChannel(AndroidHTTPDownloader); - -namespace FrontendCommon { - -AndroidHTTPDownloader::AndroidHTTPDownloader() : HTTPDownloader() {} - -AndroidHTTPDownloader::~AndroidHTTPDownloader() -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - if (m_URLDownloader_class) - env->DeleteGlobalRef(m_URLDownloader_class); -} - -std::unique_ptr HTTPDownloader::Create(const char* user_agent) -{ - std::unique_ptr instance(std::make_unique()); - if (!instance->Initialize(user_agent)) - return {}; - - return instance; -} - -bool AndroidHTTPDownloader::Initialize(const char* user_agent) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - jclass klass = env->FindClass("com/github/stenzek/duckstation/URLDownloader"); - if (!klass) - return false; - - m_URLDownloader_class = static_cast(env->NewGlobalRef(klass)); - if (!m_URLDownloader_class) - return false; - - m_URLDownloader_constructor = env->GetMethodID(klass, "", "(Ljava/lang/String;)V"); - m_URLDownloader_get = env->GetMethodID(klass, "get", "(Ljava/lang/String;)Z"); - m_URLDownloader_post = env->GetMethodID(klass, "post", "(Ljava/lang/String;[B)Z"); - m_URLDownloader_getStatusCode = env->GetMethodID(klass, "getStatusCode", "()I"); - m_URLDownloader_getData = env->GetMethodID(klass, "getData", "()[B"); - if (!m_URLDownloader_constructor || !m_URLDownloader_get || !m_URLDownloader_post || !m_URLDownloader_getStatusCode || - !m_URLDownloader_getData) - { - return false; - } - - m_user_agent = user_agent; - m_thread_pool = std::make_unique(m_max_active_requests); - return true; -} - -void AndroidHTTPDownloader::ProcessRequest(Request* req) -{ - std::unique_lock cancel_lock(m_cancel_mutex); - if (req->closed.load()) - return; - - cancel_lock.unlock(); - req->status_code = -1; - req->start_time = Common::Timer::GetValue(); - - // TODO: Move to Java side... - JNIEnv* env; - if (AndroidHelpers::GetJavaVM()->AttachCurrentThread(&env, nullptr) == JNI_OK) - { - jstring url_string = env->NewStringUTF(req->url.c_str()); - jstring user_agent_string = env->NewStringUTF(m_user_agent.c_str()); - - jobject obj = env->NewObject(m_URLDownloader_class, m_URLDownloader_constructor, user_agent_string); - jboolean result; - if (req->post_data.empty()) - { - result = env->CallBooleanMethod(obj, m_URLDownloader_get, url_string); - } - else - { - jbyteArray post_data = env->NewByteArray(static_cast(req->post_data.size())); - env->SetByteArrayRegion(post_data, 0, static_cast(req->post_data.size()), - reinterpret_cast(req->post_data.data())); - result = env->CallBooleanMethod(obj, m_URLDownloader_post, url_string, post_data); - env->DeleteLocalRef(post_data); - } - - env->DeleteLocalRef(url_string); - env->DeleteLocalRef(user_agent_string); - - if (result) - { - req->status_code = env->CallIntMethod(obj, m_URLDownloader_getStatusCode); - - jbyteArray data = reinterpret_cast(env->CallObjectMethod(obj, m_URLDownloader_getData)); - if (data) - { - const u32 size = static_cast(env->GetArrayLength(data)); - req->data.resize(size); - if (size > 0) - { - jbyte* data_ptr = env->GetByteArrayElements(data, nullptr); - std::memcpy(req->data.data(), data_ptr, size); - env->ReleaseByteArrayElements(data, data_ptr, 0); - } - - env->DeleteLocalRef(data); - } - - Log_DevPrintf("Request for '%s' returned status code %d and %zu bytes", req->url.c_str(), req->status_code, - req->data.size()); - } - else - { - Log_ErrorPrintf("Request for '%s' failed", req->url.c_str()); - } - - env->DeleteLocalRef(obj); - AndroidHelpers::GetJavaVM()->DetachCurrentThread(); - } - else - { - Log_ErrorPrintf("AttachCurrentThread() failed"); - } - - cancel_lock.lock(); - req->state = Request::State::Complete; - if (req->closed.load()) - delete req; - else - req->closed.store(true); -} - -HTTPDownloader::Request* AndroidHTTPDownloader::InternalCreateRequest() -{ - Request* req = new Request(); - return req; -} - -void AndroidHTTPDownloader::InternalPollRequests() -{ - // noop - uses thread pool -} - -bool AndroidHTTPDownloader::StartRequest(HTTPDownloader::Request* request) -{ - Request* req = static_cast(request); - Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str()); - req->state = Request::State::Started; - req->start_time = Common::Timer::GetValue(); - m_thread_pool->Schedule(std::bind(&AndroidHTTPDownloader::ProcessRequest, this, req)); - return true; -} - -void AndroidHTTPDownloader::CloseRequest(HTTPDownloader::Request* request) -{ - std::unique_lock cancel_lock(m_cancel_mutex); - Request* req = static_cast(request); - if (req->closed.load()) - delete req; - else - req->closed.store(true); -} - -} // namespace FrontendCommon diff --git a/android/app/src/cpp/android_http_downloader.h b/android/app/src/cpp/android_http_downloader.h deleted file mode 100644 index b5774846b..000000000 --- a/android/app/src/cpp/android_http_downloader.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include "common/thirdparty/thread_pool.h" -#include "frontend-common/http_downloader.h" -#include -#include -#include -#include - -namespace FrontendCommon { - -class AndroidHTTPDownloader final : public HTTPDownloader -{ -public: - AndroidHTTPDownloader(); - ~AndroidHTTPDownloader() override; - - bool Initialize(const char* user_agent); - -protected: - Request* InternalCreateRequest() override; - void InternalPollRequests() override; - bool StartRequest(HTTPDownloader::Request* request) override; - void CloseRequest(HTTPDownloader::Request* request) override; - -private: - struct Request : HTTPDownloader::Request - { - std::atomic_bool closed{false}; - }; - - void ProcessRequest(Request* req); - - std::string m_user_agent; - std::unique_ptr m_thread_pool; - std::mutex m_cancel_mutex; - - jclass m_URLDownloader_class = nullptr; - jmethodID m_URLDownloader_constructor = nullptr; - jmethodID m_URLDownloader_get = nullptr; - jmethodID m_URLDownloader_post = nullptr; - jmethodID m_URLDownloader_getStatusCode = nullptr; - jmethodID m_URLDownloader_getData = nullptr; -}; - -} // namespace FrontendCommon diff --git a/android/app/src/cpp/android_progress_callback.cpp b/android/app/src/cpp/android_progress_callback.cpp deleted file mode 100644 index a25d02624..000000000 --- a/android/app/src/cpp/android_progress_callback.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "android_progress_callback.h" -#include "android_host_interface.h" -#include "common/assert.h" -#include "common/log.h" -Log_SetChannel(AndroidProgressCallback); - -AndroidProgressCallback::AndroidProgressCallback(JNIEnv* env, jobject java_object) : m_java_object(java_object) -{ - jclass cls = env->GetObjectClass(java_object); - m_set_title_method = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V"); - m_set_status_text_method = env->GetMethodID(cls, "setStatusText", "(Ljava/lang/String;)V"); - m_set_progress_range_method = env->GetMethodID(cls, "setProgressRange", "(I)V"); - m_set_progress_value_method = env->GetMethodID(cls, "setProgressValue", "(I)V"); - m_modal_error_method = env->GetMethodID(cls, "modalError", "(Ljava/lang/String;)V"); - m_modal_information_method = env->GetMethodID(cls, "modalInformation", "(Ljava/lang/String;)V"); - m_modal_confirmation_method = env->GetMethodID(cls, "modalConfirmation", "(Ljava/lang/String;)Z"); - Assert(m_set_status_text_method && m_set_progress_range_method && m_set_progress_value_method && - m_modal_error_method && m_modal_information_method && m_modal_confirmation_method); -} - -AndroidProgressCallback::~AndroidProgressCallback() = default; - -bool AndroidProgressCallback::IsCancelled() const -{ - return false; -} - -void AndroidProgressCallback::SetCancellable(bool cancellable) -{ - if (m_cancellable == cancellable) - return; - - BaseProgressCallback::SetCancellable(cancellable); -} - -void AndroidProgressCallback::SetTitle(const char* title) -{ - Assert(title); - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder text_jstr(env, env->NewStringUTF(title)); - env->CallVoidMethod(m_java_object, m_set_title_method, text_jstr.Get()); -} - -void AndroidProgressCallback::SetStatusText(const char* text) -{ - Assert(text); - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder text_jstr(env, env->NewStringUTF(text)); - env->CallVoidMethod(m_java_object, m_set_status_text_method, text_jstr.Get()); -} - -void AndroidProgressCallback::SetProgressRange(u32 range) -{ - BaseProgressCallback::SetProgressRange(range); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - env->CallVoidMethod(m_java_object, m_set_progress_range_method, static_cast(range)); -} - -void AndroidProgressCallback::SetProgressValue(u32 value) -{ - const u32 old_value = m_progress_value; - BaseProgressCallback::SetProgressValue(value); - if (old_value == m_progress_value) - return; - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - env->CallVoidMethod(m_java_object, m_set_progress_value_method, static_cast(value)); -} - -void AndroidProgressCallback::DisplayError(const char* message) -{ - Log_ErrorPrintf("%s", message); -} - -void AndroidProgressCallback::DisplayWarning(const char* message) -{ - Log_WarningPrintf("%s", message); -} - -void AndroidProgressCallback::DisplayInformation(const char* message) -{ - Log_InfoPrintf("%s", message); -} - -void AndroidProgressCallback::DisplayDebugMessage(const char* message) -{ - Log_DevPrintf("%s", message); -} - -void AndroidProgressCallback::ModalError(const char* message) -{ - Assert(message); - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder message_jstr(env, env->NewStringUTF(message)); - env->CallVoidMethod(m_java_object, m_modal_error_method, message_jstr.Get()); -} - -bool AndroidProgressCallback::ModalConfirmation(const char* message) -{ - Assert(message); - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder message_jstr(env, env->NewStringUTF(message)); - return env->CallBooleanMethod(m_java_object, m_modal_confirmation_method, message_jstr.Get()); -} - -void AndroidProgressCallback::ModalInformation(const char* message) -{ - Assert(message); - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder message_jstr(env, env->NewStringUTF(message)); - env->CallVoidMethod(m_java_object, m_modal_information_method, message_jstr.Get()); -} diff --git a/android/app/src/cpp/android_progress_callback.h b/android/app/src/cpp/android_progress_callback.h deleted file mode 100644 index 8b15bca18..000000000 --- a/android/app/src/cpp/android_progress_callback.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include "common/progress_callback.h" -#include - -class AndroidProgressCallback final : public BaseProgressCallback -{ -public: - AndroidProgressCallback(JNIEnv* env, jobject java_object); - ~AndroidProgressCallback(); - - bool IsCancelled() const override; - - void SetCancellable(bool cancellable) override; - void SetTitle(const char* title) override; - void SetStatusText(const char* text) override; - void SetProgressRange(u32 range) override; - void SetProgressValue(u32 value) override; - - void DisplayError(const char* message) override; - void DisplayWarning(const char* message) override; - void DisplayInformation(const char* message) override; - void DisplayDebugMessage(const char* message) override; - - void ModalError(const char* message) override; - bool ModalConfirmation(const char* message) override; - void ModalInformation(const char* message) override; - -private: - jobject m_java_object; - - jmethodID m_set_title_method; - jmethodID m_set_status_text_method; - jmethodID m_set_progress_range_method; - jmethodID m_set_progress_value_method; - jmethodID m_modal_error_method; - jmethodID m_modal_confirmation_method; - jmethodID m_modal_information_method; -}; diff --git a/android/app/src/cpp/android_settings_interface.cpp b/android/app/src/cpp/android_settings_interface.cpp deleted file mode 100644 index 52af477c8..000000000 --- a/android/app/src/cpp/android_settings_interface.cpp +++ /dev/null @@ -1,425 +0,0 @@ -#include "android_settings_interface.h" -#include "android_host_interface.h" -#include "common/assert.h" -#include "common/log.h" -#include "common/string.h" -#include "common/string_util.h" -#include -Log_SetChannel(AndroidSettingsInterface); - -ALWAYS_INLINE TinyString GetSettingKey(const char* section, const char* key) -{ - return TinyString::FromFormat("%s/%s", section, key); -} - -AndroidSettingsInterface::AndroidSettingsInterface(jobject java_context) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - jclass c_preference_manager = env->FindClass("androidx/preference/PreferenceManager"); - jclass c_preference_editor = env->FindClass("android/content/SharedPreferences$Editor"); - jclass c_set = env->FindClass("java/util/Set"); - jclass c_helper = env->FindClass("com/github/stenzek/duckstation/PreferenceHelpers"); - jmethodID m_get_default_shared_preferences = - env->GetStaticMethodID(c_preference_manager, "getDefaultSharedPreferences", - "(Landroid/content/Context;)Landroid/content/SharedPreferences;"); - Assert(c_preference_manager && c_preference_editor && c_set && c_helper && m_get_default_shared_preferences); - m_set_class = reinterpret_cast(env->NewGlobalRef(c_set)); - m_shared_preferences_editor_class = reinterpret_cast(env->NewGlobalRef(c_preference_editor)); - m_helper_class = reinterpret_cast(env->NewGlobalRef(c_helper)); - Assert(m_set_class && m_shared_preferences_editor_class && m_helper_class); - - env->DeleteLocalRef(c_set); - env->DeleteLocalRef(c_preference_editor); - env->DeleteLocalRef(c_helper); - - jobject shared_preferences = - env->CallStaticObjectMethod(c_preference_manager, m_get_default_shared_preferences, java_context); - Assert(shared_preferences); - m_java_shared_preferences = env->NewGlobalRef(shared_preferences); - Assert(m_java_shared_preferences); - env->DeleteLocalRef(c_preference_manager); - env->DeleteLocalRef(shared_preferences); - - jclass c_shared_preferences = env->GetObjectClass(m_java_shared_preferences); - m_shared_preferences_class = reinterpret_cast(env->NewGlobalRef(c_shared_preferences)); - Assert(m_shared_preferences_class); - env->DeleteLocalRef(c_shared_preferences); - - m_get_boolean = env->GetMethodID(m_shared_preferences_class, "getBoolean", "(Ljava/lang/String;Z)Z"); - m_get_int = env->GetMethodID(m_shared_preferences_class, "getInt", "(Ljava/lang/String;I)I"); - m_get_float = env->GetMethodID(m_shared_preferences_class, "getFloat", "(Ljava/lang/String;F)F"); - m_get_string = env->GetMethodID(m_shared_preferences_class, "getString", - "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); - m_get_string_set = - env->GetMethodID(m_shared_preferences_class, "getStringSet", "(Ljava/lang/String;Ljava/util/Set;)Ljava/util/Set;"); - m_set_to_array = env->GetMethodID(m_set_class, "toArray", "()[Ljava/lang/Object;"); - Assert(m_get_boolean && m_get_int && m_get_float && m_get_string && m_get_string_set && m_set_to_array); - - m_edit = env->GetMethodID(m_shared_preferences_class, "edit", "()Landroid/content/SharedPreferences$Editor;"); - m_edit_set_string = - env->GetMethodID(m_shared_preferences_editor_class, "putString", - "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); - m_edit_commit = env->GetMethodID(m_shared_preferences_editor_class, "commit", "()Z"); - m_edit_remove = env->GetMethodID(m_shared_preferences_editor_class, "remove", - "(Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"); - Assert(m_edit && m_edit_set_string && m_edit_commit && m_edit_remove); - - m_helper_clear_section = - env->GetStaticMethodID(m_helper_class, "clearSection", "(Landroid/content/SharedPreferences;Ljava/lang/String;)V"); - m_helper_add_to_string_list = env->GetStaticMethodID( - m_helper_class, "addToStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); - m_helper_remove_from_string_list = - env->GetStaticMethodID(m_helper_class, "removeFromStringList", - "(Landroid/content/SharedPreferences;Ljava/lang/String;Ljava/lang/String;)Z"); - m_helper_set_string_list = env->GetStaticMethodID( - m_helper_class, "setStringList", "(Landroid/content/SharedPreferences;Ljava/lang/String;[Ljava/lang/String;)V"); - Assert(m_helper_clear_section && m_helper_add_to_string_list && m_helper_remove_from_string_list && - m_helper_set_string_list); -} - -AndroidSettingsInterface::~AndroidSettingsInterface() -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - if (m_java_shared_preferences) - env->DeleteGlobalRef(m_java_shared_preferences); - if (m_shared_preferences_editor_class) - env->DeleteGlobalRef(m_shared_preferences_editor_class); - if (m_shared_preferences_class) - env->DeleteGlobalRef(m_shared_preferences_class); - if (m_set_class) - env->DeleteGlobalRef(m_set_class); - if (m_helper_class) - env->DeleteGlobalRef(m_helper_class); -} - -bool AndroidSettingsInterface::Save() -{ - return true; -} - -void AndroidSettingsInterface::Clear() -{ - Log_ErrorPrint("Not implemented"); -} - -int AndroidSettingsInterface::GetIntValue(const char* section, const char* key, int default_value /*= 0*/) -{ - // Some of these settings are string lists... - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder default_value_string(env, env->NewStringUTF(TinyString::FromFormat("%d", default_value))); - LocalRefHolder string_object( - env, reinterpret_cast(env->CallObjectMethod(m_java_shared_preferences, m_get_string, key_string.Get(), - default_value_string.Get()))); - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - - // it might actually be an int (e.g. seek bar preference) - const int int_value = - static_cast(env->CallIntMethod(m_java_shared_preferences, m_get_int, key_string.Get(), default_value)); - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - Log_DevPrintf("GetIntValue(%s, %s) -> %d (exception)", section, key, default_value); - return default_value; - } - - Log_DevPrintf("GetIntValue(%s, %s) -> %d (int)", section, key, int_value); - return int_value; - } - - if (!string_object) - return default_value; - - const char* data = env->GetStringUTFChars(string_object, nullptr); - Assert(data != nullptr); - Log_DevPrintf("GetIntValue(%s, %s) -> %s", section, key, data); - - std::optional value = StringUtil::FromChars(data); - env->ReleaseStringUTFChars(string_object, data); - return value.value_or(default_value); -} - -float AndroidSettingsInterface::GetFloatValue(const char* section, const char* key, float default_value /*= 0.0f*/) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder default_value_string(env, env->NewStringUTF(TinyString::FromFormat("%f", default_value))); - LocalRefHolder string_object( - env, reinterpret_cast(env->CallObjectMethod(m_java_shared_preferences, m_get_string, key_string.Get(), - default_value_string.Get()))); - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - Log_DevPrintf("GetFloatValue(%s, %s) -> %f (exception)", section, key, default_value); - return default_value; - } - - if (!string_object) - { - Log_DevPrintf("GetFloatValue(%s, %s) -> %f (null)", section, key, default_value); - return default_value; - } - - const char* data = env->GetStringUTFChars(string_object, nullptr); - Assert(data != nullptr); - Log_DevPrintf("GetFloatValue(%s, %s) -> %s", section, key, data); - - std::optional value = StringUtil::FromChars(data); - env->ReleaseStringUTFChars(string_object, data); - return value.value_or(default_value); -} - -bool AndroidSettingsInterface::GetBoolValue(const char* section, const char* key, bool default_value /*= false*/) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - jboolean bool_value = static_cast( - env->CallBooleanMethod(m_java_shared_preferences, m_get_boolean, key_string.Get(), default_value)); - if (env->ExceptionCheck()) - { - Log_DevPrintf("GetBoolValue(%s, %s) -> %u (exception)", section, key, static_cast(default_value)); - env->ExceptionClear(); - return default_value; - } - - Log_DevPrintf("GetBoolValue(%s, %s) -> %u", section, key, static_cast(bool_value)); - return bool_value; -} - -std::string AndroidSettingsInterface::GetStringValue(const char* section, const char* key, - const char* default_value /*= ""*/) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder default_value_string(env, env->NewStringUTF(default_value)); - LocalRefHolder string_object( - env, reinterpret_cast(env->CallObjectMethod(m_java_shared_preferences, m_get_string, key_string.Get(), - default_value_string.Get()))); - - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - Log_DevPrintf("GetStringValue(%s, %s) -> %s (exception)", section, key, default_value); - return default_value; - } - - if (!string_object) - { - Log_DevPrintf("GetStringValue(%s, %s) -> %s (null)", section, key, default_value); - return default_value; - } - - const std::string ret(AndroidHelpers::JStringToString(env, string_object)); - Log_DevPrintf("GetStringValue(%s, %s) -> %s", section, key, ret.c_str()); - return ret; -} - -jobject AndroidSettingsInterface::GetPreferencesEditor(JNIEnv* env) -{ - return env->CallObjectMethod(m_java_shared_preferences, m_edit); -} - -void AndroidSettingsInterface::CheckForException(JNIEnv* env, const char* task) -{ - if (!env->ExceptionCheck()) - return; - - Log_ErrorPrintf("JNI exception during %s", task); - env->ExceptionClear(); -} - -void AndroidSettingsInterface::SetIntValue(const char* section, const char* key, int value) -{ - Log_DevPrintf("SetIntValue(\"%s\", \"%s\", %d)", section, key, value); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder editor(env, GetPreferencesEditor(env)); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder str_value(env, env->NewStringUTF(TinyString::FromFormat("%d", value))); - - LocalRefHolder dummy(env, - env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); - env->CallBooleanMethod(editor, m_edit_commit); - - CheckForException(env, "SetIntValue"); -} - -void AndroidSettingsInterface::SetFloatValue(const char* section, const char* key, float value) -{ - Log_DevPrintf("SetFloatValue(\"%s\", \"%s\", %f)", section, key, value); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder editor(env, GetPreferencesEditor(env)); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder str_value(env, env->NewStringUTF(TinyString::FromFormat("%f", value))); - - LocalRefHolder dummy(env, - env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); - env->CallBooleanMethod(editor, m_edit_commit); - - CheckForException(env, "SetFloatValue"); -} - -void AndroidSettingsInterface::SetBoolValue(const char* section, const char* key, bool value) -{ - Log_DevPrintf("SetBoolValue(\"%s\", \"%s\", %u)", section, key, static_cast(value)); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder editor(env, GetPreferencesEditor(env)); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder str_value(env, env->NewStringUTF(value ? "true" : "false")); - - LocalRefHolder dummy(env, - env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); - env->CallBooleanMethod(editor, m_edit_commit); - - CheckForException(env, "SetBoolValue"); -} - -void AndroidSettingsInterface::SetStringValue(const char* section, const char* key, const char* value) -{ - Log_DevPrintf("SetStringValue(\"%s\", \"%s\", \"%s\")", section, key, value); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder editor(env, GetPreferencesEditor(env)); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder str_value(env, env->NewStringUTF(value)); - - LocalRefHolder dummy(env, - env->CallObjectMethod(editor, m_edit_set_string, key_string.Get(), str_value.Get())); - env->CallBooleanMethod(editor, m_edit_commit); - - CheckForException(env, "SetStringValue"); -} - -void AndroidSettingsInterface::DeleteValue(const char* section, const char* key) -{ - Log_DevPrintf("DeleteValue(\"%s\", \"%s\")", section, key); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder editor(env, GetPreferencesEditor(env)); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder dummy(env, env->CallObjectMethod(editor, m_edit_remove, key_string.Get())); - env->CallBooleanMethod(editor, m_edit_commit); - - CheckForException(env, "DeleteValue"); -} - -void AndroidSettingsInterface::ClearSection(const char* section) -{ - Log_DevPrintf("ClearSection(\"%s\")", section); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder str_section(env, env->NewStringUTF(section)); - env->CallStaticVoidMethod(m_helper_class, m_helper_clear_section, m_java_shared_preferences, str_section.Get()); - - CheckForException(env, "ClearSection"); -} - -std::vector AndroidSettingsInterface::GetStringList(const char* section, const char* key) -{ - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder values_set( - env, env->CallObjectMethod(m_java_shared_preferences, m_get_string_set, key_string.Get(), nullptr)); - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - - // this might just be a string, not a string set - LocalRefHolder string_object(env, reinterpret_cast(env->CallObjectMethod( - m_java_shared_preferences, m_get_string, key_string.Get(), nullptr))); - - if (!env->ExceptionCheck()) - { - std::vector ret; - if (string_object) - ret.push_back(AndroidHelpers::JStringToString(env, string_object)); - - return ret; - } - - env->ExceptionClear(); - return {}; - } - - if (!values_set) - return {}; - - LocalRefHolder values_array( - env, reinterpret_cast(env->CallObjectMethod(values_set, m_set_to_array))); - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - return {}; - } - - if (!values_array) - return {}; - - jsize size = env->GetArrayLength(values_array); - std::vector values; - values.reserve(size); - for (jsize i = 0; i < size; i++) - { - jstring str = reinterpret_cast(env->GetObjectArrayElement(values_array, i)); - values.push_back(AndroidHelpers::JStringToString(env, str)); - env->DeleteLocalRef(str); - } - - return values; -} - -void AndroidSettingsInterface::SetStringList(const char* section, const char* key, - const std::vector& items) -{ - Log_DevPrintf("SetStringList(\"%s\", \"%s\")", section, key); - if (items.empty()) - { - DeleteValue(section, key); - return; - } - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder items_array( - env, env->NewObjectArray(static_cast(items.size()), AndroidHelpers::GetStringClass(), nullptr)); - for (size_t i = 0; i < items.size(); i++) - { - LocalRefHolder item_jstr(env, env->NewStringUTF(items[i].c_str())); - env->SetObjectArrayElement(items_array, static_cast(i), item_jstr); - } - - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - env->CallStaticVoidMethod(m_helper_class, m_helper_set_string_list, m_java_shared_preferences, key_string.Get(), - items_array.Get()); - - CheckForException(env, "SetStringList"); -} - -bool AndroidSettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item) -{ - Log_DevPrintf("RemoveFromStringList(\"%s\", \"%s\", \"%s\")", section, key, item); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder item_string(env, env->NewStringUTF(item)); - const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_remove_from_string_list, - m_java_shared_preferences, key_string.Get(), item_string.Get()); - CheckForException(env, "RemoveFromStringList"); - return result; -} - -bool AndroidSettingsInterface::AddToStringList(const char* section, const char* key, const char* item) -{ - Log_DevPrintf("AddToStringList(\"%s\", \"%s\", \"%s\")", section, key, item); - - JNIEnv* env = AndroidHelpers::GetJNIEnv(); - LocalRefHolder key_string(env, env->NewStringUTF(GetSettingKey(section, key))); - LocalRefHolder item_string(env, env->NewStringUTF(item)); - const bool result = env->CallStaticBooleanMethod(m_helper_class, m_helper_add_to_string_list, - m_java_shared_preferences, key_string.Get(), item_string.Get()); - CheckForException(env, "AddToStringList"); - return result; -} diff --git a/android/app/src/cpp/android_settings_interface.h b/android/app/src/cpp/android_settings_interface.h deleted file mode 100644 index 542a2f90e..000000000 --- a/android/app/src/cpp/android_settings_interface.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include "core/settings.h" -#include - -class AndroidSettingsInterface : public SettingsInterface -{ -public: - AndroidSettingsInterface(jobject java_context); - ~AndroidSettingsInterface(); - - bool Save() override; - void Clear() override; - - int GetIntValue(const char* section, const char* key, int default_value = 0) override; - float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) override; - bool GetBoolValue(const char* section, const char* key, bool default_value = false) override; - std::string GetStringValue(const char* section, const char* key, const char* default_value = "") override; - - void SetIntValue(const char* section, const char* key, int value) override; - void SetFloatValue(const char* section, const char* key, float value) override; - void SetBoolValue(const char* section, const char* key, bool value) override; - void SetStringValue(const char* section, const char* key, const char* value) override; - void DeleteValue(const char* section, const char* key) override; - void ClearSection(const char* section) override; - - std::vector GetStringList(const char* section, const char* key) override; - void SetStringList(const char* section, const char* key, const std::vector& items) override; - bool RemoveFromStringList(const char* section, const char* key, const char* item) override; - bool AddToStringList(const char* section, const char* key, const char* item) override; - -private: - jobject GetPreferencesEditor(JNIEnv* env); - void CheckForException(JNIEnv* env, const char* task); - - jclass m_set_class{}; - jclass m_shared_preferences_class{}; - jclass m_shared_preferences_editor_class{}; - jclass m_helper_class{}; - jobject m_java_shared_preferences{}; - jmethodID m_get_boolean{}; - jmethodID m_get_int{}; - jmethodID m_get_float{}; - jmethodID m_get_string{}; - jmethodID m_get_string_set{}; - jmethodID m_edit{}; - jmethodID m_edit_set_string{}; - jmethodID m_edit_commit{}; - jmethodID m_edit_remove{}; - jmethodID m_set_to_array{}; - jmethodID m_helper_clear_section{}; - jmethodID m_helper_add_to_string_list{}; - jmethodID m_helper_remove_from_string_list{}; - jmethodID m_helper_set_string_list{}; -}; diff --git a/android/app/src/cpp/opensles_audio_stream.cpp b/android/app/src/cpp/opensles_audio_stream.cpp deleted file mode 100644 index 70f2c207c..000000000 --- a/android/app/src/cpp/opensles_audio_stream.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include "opensles_audio_stream.h" -#include "common/assert.h" -#include "common/log.h" -#include -Log_SetChannel(OpenSLESAudioStream); - -// Based off Dolphin's OpenSLESStream class. - -OpenSLESAudioStream::OpenSLESAudioStream() = default; - -OpenSLESAudioStream::~OpenSLESAudioStream() -{ - if (IsOpen()) - OpenSLESAudioStream::CloseDevice(); -} - -std::unique_ptr OpenSLESAudioStream::Create() -{ - return std::make_unique(); -} - -bool OpenSLESAudioStream::OpenDevice() -{ - DebugAssert(!IsOpen()); - - SLresult res = slCreateEngine(&m_engine, 0, nullptr, 0, nullptr, nullptr); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("slCreateEngine failed: %d", res); - return false; - } - - res = (*m_engine)->Realize(m_engine, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("Realize(Engine) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_engine)->GetInterface(m_engine, SL_IID_ENGINE, &m_engine_engine); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("GetInterface(SL_IID_ENGINE) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix, 0, 0, 0); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("CreateOutputMix failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_output_mix)->Realize(m_output_mix, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("Realize(OutputMix) mix failed: %d", res); - CloseDevice(); - return false; - } - - SLDataLocator_AndroidSimpleBufferQueue dloc_bq{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_BUFFERS}; - SLDataFormat_PCM format = {SL_DATAFORMAT_PCM, - m_channels, - m_output_sample_rate * 1000u, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, - SL_BYTEORDER_LITTLEENDIAN}; - SLDataSource dsrc{&dloc_bq, &format}; - SLDataLocator_OutputMix dloc_outputmix{SL_DATALOCATOR_OUTPUTMIX, m_output_mix}; - SLDataSink dsink{&dloc_outputmix, nullptr}; - - const std::array ap_interfaces = {{SL_IID_BUFFERQUEUE, SL_IID_VOLUME}}; - const std::array ap_interfaces_req = {{true, true}}; - res = (*m_engine_engine) - ->CreateAudioPlayer(m_engine_engine, &m_player, &dsrc, &dsink, static_cast(ap_interfaces.size()), - ap_interfaces.data(), ap_interfaces_req.data()); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("CreateAudioPlayer failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_player)->Realize(m_player, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("Realize(AudioPlayer) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_player)->GetInterface(m_player, SL_IID_PLAY, &m_play_interface); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("GetInterface(SL_IID_PLAY) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_player)->GetInterface(m_player, SL_IID_BUFFERQUEUE, &m_buffer_queue_interface); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("GetInterface(SL_IID_BUFFERQUEUE) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_player)->GetInterface(m_player, SL_IID_VOLUME, &m_volume_interface); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("GetInterface(SL_IID_VOLUME) failed: %d", res); - CloseDevice(); - return false; - } - - res = (*m_buffer_queue_interface)->RegisterCallback(m_buffer_queue_interface, BufferCallback, this); - if (res != SL_RESULT_SUCCESS) - { - Log_ErrorPrintf("Failed to register callback: %d", res); - CloseDevice(); - return false; - } - - for (u32 i = 0; i < NUM_BUFFERS; i++) - m_buffers[i] = std::make_unique(m_buffer_size * m_channels); - - Log_InfoPrintf("OpenSL ES device opened: %uhz, %u channels, %u buffer size, %u buffers", m_output_sample_rate, - m_channels, m_buffer_size, NUM_BUFFERS); - return true; -} - -void OpenSLESAudioStream::PauseDevice(bool paused) -{ - if (m_paused == paused) - return; - - SLresult res = - (*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING); - if (res != SL_RESULT_SUCCESS) - Log_ErrorPrintf("SetPlayState failed: %d", res); - - if (!paused && !m_buffer_enqueued) - { - m_buffer_enqueued = true; - EnqueueBuffer(); - } - - m_paused = paused; -} - -void OpenSLESAudioStream::CloseDevice() -{ - m_buffers = {}; - m_current_buffer = 0; - m_paused = true; - m_buffer_enqueued = false; - - if (m_player) - { - (*m_player)->Destroy(m_player); - m_volume_interface = {}; - m_buffer_queue_interface = {}; - m_play_interface = {}; - m_player = {}; - } - if (m_output_mix) - { - (*m_output_mix)->Destroy(m_output_mix); - m_output_mix = {}; - } - (*m_engine)->Destroy(m_engine); - m_engine_engine = {}; - m_engine = {}; -} - -void OpenSLESAudioStream::SetOutputVolume(u32 volume) -{ - const SLmillibel attenuation = (volume == 0) ? - SL_MILLIBEL_MIN : - static_cast(2000.0f * std::log10(static_cast(volume) / 100.0f)); - SLresult res = (*m_volume_interface)->SetVolumeLevel(m_volume_interface, attenuation); - if (res != SL_RESULT_SUCCESS) - Log_ErrorPrintf("SetVolumeLevel failed: %d", res); -} - -void OpenSLESAudioStream::EnqueueBuffer() -{ - SampleType* samples = m_buffers[m_current_buffer].get(); - ReadFrames(samples, m_buffer_size, false); - - SLresult res = (*m_buffer_queue_interface) - ->Enqueue(m_buffer_queue_interface, samples, m_buffer_size * m_channels * sizeof(SampleType)); - if (res != SL_RESULT_SUCCESS) - Log_ErrorPrintf("Enqueue buffer failed: %d", res); - - m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS; -} - -void OpenSLESAudioStream::BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context) -{ - OpenSLESAudioStream* const this_ptr = static_cast(context); - this_ptr->EnqueueBuffer(); -} - -void OpenSLESAudioStream::FramesAvailable() {} diff --git a/android/app/src/cpp/opensles_audio_stream.h b/android/app/src/cpp/opensles_audio_stream.h deleted file mode 100644 index 83c54aa6e..000000000 --- a/android/app/src/cpp/opensles_audio_stream.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include "common/audio_stream.h" -#include -#include -#include -#include - -class OpenSLESAudioStream final : public AudioStream -{ -public: - OpenSLESAudioStream(); - ~OpenSLESAudioStream(); - - static std::unique_ptr Create(); - - void SetOutputVolume(u32 volume) override; - -protected: - enum : u32 - { - NUM_BUFFERS = 2 - }; - - ALWAYS_INLINE bool IsOpen() const { return (m_engine != nullptr); } - - bool OpenDevice() override; - void PauseDevice(bool paused) override; - void CloseDevice() override; - void FramesAvailable() override; - - void EnqueueBuffer(); - - static void BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context); - - SLObjectItf m_engine{}; - SLEngineItf m_engine_engine{}; - SLObjectItf m_output_mix{}; - - SLObjectItf m_player{}; - SLPlayItf m_play_interface{}; - SLAndroidSimpleBufferQueueItf m_buffer_queue_interface{}; - SLVolumeItf m_volume_interface{}; - - std::array, NUM_BUFFERS> m_buffers; - u32 m_current_buffer = 0; - bool m_paused = true; - bool m_buffer_enqueued = false; -}; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index d1181b437..000000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/ic_launcher-web.png b/android/app/src/main/ic_launcher-web.png deleted file mode 100644 index 353ac1fe7..000000000 Binary files a/android/app/src/main/ic_launcher-web.png and /dev/null differ diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/Achievement.java b/android/app/src/main/java/com/github/stenzek/duckstation/Achievement.java deleted file mode 100644 index ee4477c83..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/Achievement.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -public final class Achievement { - public static final int CATEGORY_LOCAL = 0; - public static final int CATEGORY_CORE = 3; - public static final int CATEGORY_UNOFFICIAL = 5; - - private final int id; - private final String name; - private final String description; - private final String lockedBadgePath; - private final String unlockedBadgePath; - private final int points; - private final boolean locked; - - public Achievement(int id, String name, String description, String lockedBadgePath, - String unlockedBadgePath, int points, boolean locked) { - this.id = id; - this.name = name; - this.description = description; - this.lockedBadgePath = lockedBadgePath; - this.unlockedBadgePath = unlockedBadgePath; - this.points = points; - this.locked = locked; - } - - /** - * Returns true if challenge mode will be enabled when a game is started. - * Does not depend on the emulation running. - * - * @param context context to pull settings from - * @return true if challenge mode will be used, false otherwise - */ - public static boolean willChallengeModeBeEnabled(Context context) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return prefs.getBoolean("Cheevos/Enabled", false) && - prefs.getBoolean("Cheevos/ChallengeMode", false); - } - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public String getLockedBadgePath() { - return lockedBadgePath; - } - - public String getUnlockedBadgePath() { - return unlockedBadgePath; - } - - public int getPoints() { - return points; - } - - public boolean isLocked() { - return locked; - } - - public String getBadgePath() { - return locked ? lockedBadgePath : unlockedBadgePath; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AchievementListFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/AchievementListFragment.java deleted file mode 100644 index 04b229ad4..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AchievementListFragment.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Configuration; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.Arrays; -import java.util.Comparator; - -public class AchievementListFragment extends DialogFragment { - - private RecyclerView mRecyclerView; - private AchievementListFragment.ViewAdapter mAdapter; - private final Achievement[] mAchievements; - private DialogInterface.OnDismissListener mOnDismissListener; - - public AchievementListFragment(Achievement[] achievements) { - mAchievements = achievements; - sortAchievements(); - } - - public void setOnDismissListener(DialogInterface.OnDismissListener l) { - mOnDismissListener = l; - } - - @Override - public void onDismiss(@NonNull DialogInterface dialog) { - if (mOnDismissListener != null) - mOnDismissListener.onDismiss(dialog); - - super.onDismiss(dialog); - } - - @Override - public void onResume() { - super.onResume(); - - if (getDialog() == null) - return; - - final boolean isLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); - final float scale = (float) getContext().getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT; - final int width = Math.round((isLandscape ? 700.0f : 400.0f) * scale); - final int height = Math.round((isLandscape ? 400.0f : 700.0f) * scale); - getDialog().getWindow().setLayout(width, height); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_achievement_list, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mAdapter = new AchievementListFragment.ViewAdapter(getContext(), mAchievements); - - mRecyclerView = view.findViewById(R.id.recyclerView); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(), - DividerItemDecoration.VERTICAL)); - - fillHeading(view); - } - - private void fillHeading(@NonNull View view) { - final AndroidHostInterface hi = AndroidHostInterface.getInstance(); - final String gameTitle = hi.getCheevoGameTitle(); - if (gameTitle != null) { - final String formattedTitle = hi.isCheevosChallengeModeActive() ? - String.format(getString(R.string.achievement_title_challenge_mode_format_string), gameTitle) : - gameTitle; - ((TextView) view.findViewById(R.id.title)).setText(formattedTitle); - } - - final int cheevoCount = hi.getCheevoCount(); - final int unlockedCheevoCount = hi.getUnlockedCheevoCount(); - final String summary = String.format(getString(R.string.achievement_summary_format_string), - unlockedCheevoCount, cheevoCount, hi.getCheevoPointsForGame(), hi.getCheevoMaximumPointsForGame()); - ((TextView) view.findViewById(R.id.summary)).setText(summary); - - ProgressBar pb = ((ProgressBar) view.findViewById(R.id.progressBar)); - pb.setMax(cheevoCount); - pb.setProgress(unlockedCheevoCount); - - final ImageView icon = ((ImageView) view.findViewById(R.id.icon)); - final String badgePath = hi.getCheevoGameIconPath(); - if (badgePath != null) { - new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, badgePath); - } - } - - private void sortAchievements() { - Arrays.sort(mAchievements, (o1, o2) -> { - if (o2.isLocked() && !o1.isLocked()) - return -1; - else if (o1.isLocked() && !o2.isLocked()) - return 1; - - return o1.getName().compareTo(o2.getName()); - }); - } - - private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - private final View mItemView; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - mItemView = itemView; - mItemView.setOnClickListener(this); - mItemView.setOnLongClickListener(this); - } - - public void bindToEntry(Achievement cheevo) { - ImageView icon = ((ImageView) mItemView.findViewById(R.id.icon)); - icon.setImageDrawable(mItemView.getContext().getDrawable(R.drawable.ic_baseline_lock_24)); - - final String badgePath = cheevo.getBadgePath(); - if (badgePath != null) { - new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, badgePath); - } - - ((TextView) mItemView.findViewById(R.id.title)).setText(cheevo.getName()); - ((TextView) mItemView.findViewById(R.id.description)).setText(cheevo.getDescription()); - - ((ImageView) mItemView.findViewById(R.id.locked_icon)).setImageDrawable( - mItemView.getContext().getDrawable(cheevo.isLocked() ? R.drawable.ic_baseline_lock_24 : R.drawable.ic_baseline_lock_open_24)); - - final String pointsString = String.format(mItemView.getContext().getString(R.string.achievement_points_format_string), cheevo.getPoints()); - ((TextView) mItemView.findViewById(R.id.points)).setText(pointsString); - } - - @Override - public void onClick(View v) { - // - } - - @Override - public boolean onLongClick(View v) { - return false; - } - } - - private static class ViewAdapter extends RecyclerView.Adapter { - private final LayoutInflater mInflater; - private final Achievement[] mAchievements; - - public ViewAdapter(@NonNull Context context, Achievement[] achievements) { - mInflater = LayoutInflater.from(context); - mAchievements = achievements; - } - - @NonNull - @Override - public AchievementListFragment.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new AchievementListFragment.ViewHolder(mInflater.inflate(R.layout.layout_achievement_entry, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull AchievementListFragment.ViewHolder holder, int position) { - holder.bindToEntry(mAchievements[position]); - } - - @Override - public int getItemCount() { - return (mAchievements != null) ? mAchievements.length : 0; - } - - @Override - public int getItemViewType(int position) { - return R.layout.layout_game_list_entry; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AchievementSettingsFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/AchievementSettingsFragment.java deleted file mode 100644 index 0b4c1a149..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AchievementSettingsFragment.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceScreen; - -import java.net.URLEncoder; -import java.text.DateFormat; -import java.util.Date; -import java.util.Locale; - -public class AchievementSettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener { - private static final String REGISTER_URL = "http://retroachievements.org/createaccount.php"; - private static final String PROFILE_URL_PREFIX = "https://retroachievements.org/user/"; - - private boolean isLoggedIn = false; - private String username; - private String loginTokenTime; - - public AchievementSettingsFragment() { - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.achievement_preferences, rootKey); - updateViews(); - } - - private void updateViews() { - final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); - - username = prefs.getString("Cheevos/Username", ""); - isLoggedIn = (username != null && !username.isEmpty()); - if (isLoggedIn) { - try { - final String loginTokenTimeString = prefs.getString("Cheevos/LoginTimestamp", ""); - final long loginUnixTimestamp = Long.parseLong(loginTokenTimeString); - - // TODO: Extract to a helper function. - final Date date = new Date(loginUnixTimestamp * 1000); - final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, Locale.getDefault()); - loginTokenTime = format.format(date); - } catch (Exception e) { - loginTokenTime = null; - } - } - - final PreferenceScreen preferenceScreen = getPreferenceScreen(); - - Preference preference = preferenceScreen.findPreference("Cheevos/ChallengeMode"); - if (preference != null) { - // toggling this is disabled while it's running to avoid the whole power off thing - preference.setEnabled(!AndroidHostInterface.getInstance().isEmulationThreadRunning()); - } - - preference = preferenceScreen.findPreference("Cheevos/Login"); - if (preference != null) - { - preference.setVisible(!isLoggedIn); - preference.setOnPreferenceClickListener(this); - } - - preference = preferenceScreen.findPreference("Cheevos/Register"); - if (preference != null) - { - preference.setVisible(!isLoggedIn); - preference.setOnPreferenceClickListener(this); - } - - preference = preferenceScreen.findPreference("Cheevos/Logout"); - if (preference != null) - { - preference.setVisible(isLoggedIn); - preference.setOnPreferenceClickListener(this); - } - - preference = preferenceScreen.findPreference("Cheevos/Username"); - if (preference != null) - { - preference.setVisible(isLoggedIn); - preference.setSummary((username != null) ? username : ""); - } - - preference = preferenceScreen.findPreference("Cheevos/LoginTokenTime"); - if (preference != null) - { - preference.setVisible(isLoggedIn); - preference.setSummary((loginTokenTime != null) ? loginTokenTime : ""); - } - - preference = preferenceScreen.findPreference("Cheevos/ViewProfile"); - if (preference != null) - { - preference.setVisible(isLoggedIn); - preference.setOnPreferenceClickListener(this); - } - } - - @Override - public boolean onPreferenceClick(Preference preference) { - final String key = preference.getKey(); - if (key == null) - return false; - - switch (key) - { - case "Cheevos/Login": - { - handleLogin(); - return true; - } - - case "Cheevos/Logout": - { - handleLogout(); - return true; - } - - case "Cheevos/Register": - { - openUrl(REGISTER_URL); - return true; - } - - case "Cheevos/ViewProfile": - { - final String profileUrl = getProfileUrl(username); - if (profileUrl != null) - openUrl(profileUrl); - - return true; - } - - default: - return false; - } - } - - private void openUrl(String url) { - final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - } - - private void handleLogin() { - LoginDialogFragment loginDialog = new LoginDialogFragment(this); - loginDialog.show(getFragmentManager(), "fragment_achievement_login"); - } - - private void handleLogout() { - final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(R.string.settings_achievements_confirm_logout_title); - builder.setMessage(R.string.settings_achievements_confirm_logout_message); - builder.setPositiveButton(R.string.settings_achievements_logout, (dialog, which) -> { - AndroidHostInterface.getInstance().cheevosLogout(); - updateViews(); - }); - builder.setNegativeButton(R.string.achievement_settings_login_cancel_button, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - private static String getProfileUrl(String username) { - try { - final String encodedUsername = URLEncoder.encode(username, "UTF-8"); - return PROFILE_URL_PREFIX + encodedUsername; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public static class LoginDialogFragment extends DialogFragment { - private AchievementSettingsFragment mParent; - - public LoginDialogFragment(AchievementSettingsFragment parent) { - mParent = parent; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_achievements_login, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - ((Button)view.findViewById(R.id.login)).setOnClickListener((View.OnClickListener) v -> doLogin()); - ((Button)view.findViewById(R.id.cancel)).setOnClickListener((View.OnClickListener) v -> dismiss()); - } - - private static class LoginTask extends AsyncTask { - private LoginDialogFragment mParent; - private String mUsername; - private String mPassword; - private boolean mResult; - - public LoginTask(LoginDialogFragment parent, String username, String password) { - mParent = parent; - mUsername = username; - mPassword = password; - } - - @Override - protected Void doInBackground(Void... voids) { - final Activity activity = mParent.getActivity(); - if (activity == null) - return null; - - mResult = AndroidHostInterface.getInstance().cheevosLogin(mUsername, mPassword); - - activity.runOnUiThread(() -> { - if (!mResult) { - ((TextView) mParent.getView().findViewById(R.id.error)).setText(R.string.achievement_settings_login_failed); - mParent.enableUi(true); - return; - } - - mParent.mParent.updateViews(); - mParent.dismiss(); - }); - - return null; - } - } - - private void doLogin() { - final View rootView = getView(); - final String username = ((EditText)rootView.findViewById(R.id.username)).getText().toString(); - final String password = ((EditText)rootView.findViewById(R.id.password)).getText().toString(); - if (username == null || username.length() == 0 || password == null || password.length() == 0) - return; - - enableUi(false); - ((TextView)rootView.findViewById(R.id.error)).setText(""); - new LoginDialogFragment.LoginTask(this, username, password).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void enableUi(boolean enabled) { - final View rootView = getView(); - ((EditText)rootView.findViewById(R.id.username)).setEnabled(enabled); - ((EditText)rootView.findViewById(R.id.password)).setEnabled(enabled); - ((Button)rootView.findViewById(R.id.login)).setEnabled(enabled); - ((Button)rootView.findViewById(R.id.cancel)).setEnabled(enabled); - ((ProgressBar)rootView.findViewById(R.id.progressBar)).setVisibility(enabled ? View.GONE : View.VISIBLE); - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java deleted file mode 100644 index 48d70f7d3..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.AssetManager; -import android.os.Environment; -import android.os.Process; -import android.util.Log; -import android.view.Surface; -import android.widget.Toast; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Locale; - -public class AndroidHostInterface { - public final static int DISPLAY_ALIGNMENT_TOP_OR_LEFT = 0; - public final static int DISPLAY_ALIGNMENT_CENTER = 1; - public final static int DISPLAY_ALIGNMENT_RIGHT_OR_BOTTOM = 2; - - public final static int CONTROLLER_AXIS_TYPE_FULL = 0; - public final static int CONTROLLER_AXIS_TYPE_HALF = 1; - - private long mNativePointer; - private Context mContext; - private FileHelper mFileHelper; - private EmulationActivity mEmulationActivity; - - public AndroidHostInterface(Context context, FileHelper fileHelper) { - this.mContext = context; - this.mFileHelper = fileHelper; - } - - public void reportError(String message) { - Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); - } - - public void reportMessage(String message) { - Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); - } - - public InputStream openAssetStream(String path) { - try { - return mContext.getAssets().open(path, AssetManager.ACCESS_STREAMING); - } catch (IOException e) { - return null; - } - } - - public void setContext(Context context) { - mContext = context; - } - - public EmulationActivity getEmulationActivity() { return mEmulationActivity; } - - static public native String getScmVersion(); - - static public native String getFullScmVersion(); - - static public native boolean setThreadAffinity(int[] cpus); - - static public native AndroidHostInterface create(Context context, FileHelper fileHelper, String userDirectory); - - public native boolean isEmulationThreadRunning(); - - public native boolean runEmulationThread(EmulationActivity emulationActivity, String filename, boolean resumeState, String state_filename); - - public native boolean isEmulationThreadPaused(); - - public native void pauseEmulationThread(boolean paused); - - public native void stopEmulationThreadLoop(); - - public native boolean hasSurface(); - - public native void surfaceChanged(Surface surface, int format, int width, int height); - - public native void setControllerButtonState(int index, int buttonCode, boolean pressed); - - public native void setControllerAxisState(int index, int axisCode, float value); - - public native void setControllerAutoFireState(int controllerIndex, int autoFireIndex, boolean active); - - public native void setMousePosition(int positionX, int positionY); - - public static native int getControllerButtonCode(String controllerType, String buttonName); - - public static native int getControllerAxisCode(String controllerType, String axisName); - - public static native int getControllerAxisType(String controllerType, String axisName); - - public static native String[] getControllerButtonNames(String controllerType); - - public static native String[] getControllerAxisNames(String controllerType); - - public static native int getControllerVibrationMotorCount(String controllerType); - - public native void handleControllerButtonEvent(int controllerIndex, int buttonIndex, boolean pressed); - - public native void handleControllerAxisEvent(int controllerIndex, int axisIndex, float value); - - public native boolean hasControllerButtonBinding(int controllerIndex, int buttonIndex); - - public native void toggleControllerAnalogMode(); - - public native String[] getInputProfileNames(); - - public native boolean loadInputProfile(String name); - - public native boolean saveInputProfile(String name); - - public native HotkeyInfo[] getHotkeyInfoList(); - - public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase, AndroidProgressCallback progressCallback); - - public native GameListEntry[] getGameListEntries(); - - public native GameListEntry getGameListEntry(String path); - - public native String getGameSettingValue(String path, String key); - - public native void setGameSettingValue(String path, String key, String value); - - public native void resetSystem(); - - public native void loadState(boolean global, int slot); - - public native void saveState(boolean global, int slot); - - public native void saveResumeState(boolean waitForCompletion); - - public native void applySettings(); - public native void updateInputMap(); - - public native void setDisplayAlignment(int alignment); - - public native PatchCode[] getPatchCodeList(); - - public native void setPatchCodeEnabled(int index, boolean enabled); - - public native boolean importPatchCodesFromString(String str); - - public native void addOSDMessage(String message, float duration); - - public native boolean hasAnyBIOSImages(); - - public native String importBIOSImage(byte[] data); - - public native boolean isFastForwardEnabled(); - - public native void setFastForwardEnabled(boolean enabled); - - public native boolean hasMediaSubImages(); - - public native String[] getMediaSubImageTitles(); - - public native int getMediaSubImageIndex(); - - public native boolean switchMediaSubImage(int index); - - public native boolean setMediaFilename(String filename); - - public native SaveStateInfo[] getSaveStateInfo(boolean includeEmpty); - - public native void setFullscreenUINotificationVerticalPosition(float position, float direction); - - public native boolean isCheevosActive(); - public native boolean isCheevosChallengeModeActive(); - public native Achievement[] getCheevoList(); - public native int getCheevoCount(); - public native int getUnlockedCheevoCount(); - public native int getCheevoPointsForGame(); - public native int getCheevoMaximumPointsForGame(); - public native String getCheevoGameTitle(); - public native String getCheevoGameIconPath(); - public native boolean cheevosLogin(String username, String password); - public native void cheevosLogout(); - - static { - System.loadLibrary("duckstation-native"); - } - - static private AndroidHostInterface mInstance; - static private String mUserDirectory; - - static public boolean createInstance(Context context) { - // Set user path. - mUserDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); - if (mUserDirectory.isEmpty()) - mUserDirectory = "/sdcard"; - - mUserDirectory += "/duckstation"; - Log.i("AndroidHostInterface", "User directory: " + mUserDirectory); - mInstance = create(context, new FileHelper(context), mUserDirectory); - return mInstance != null; - } - - static public boolean hasInstance() { - return mInstance != null; - } - - static public AndroidHostInterface getInstance() { - return mInstance; - } - - static public String getUserDirectory() { return mUserDirectory; } - - static public boolean hasInstanceAndEmulationThreadIsRunning() { - return hasInstance() && getInstance().isEmulationThreadRunning(); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidProgressCallback.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidProgressCallback.java deleted file mode 100644 index a77b9b3b9..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidProgressCallback.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.Activity; -import android.app.ProgressDialog; - -import androidx.appcompat.app.AlertDialog; - -public class AndroidProgressCallback { - private Activity mContext; - private ProgressDialog mDialog; - - public AndroidProgressCallback(Activity context) { - mContext = context; - mDialog = new ProgressDialog(context); - mDialog.setCancelable(false); - mDialog.setCanceledOnTouchOutside(false); - mDialog.setMessage(context.getString(R.string.android_progress_callback_please_wait)); - mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mDialog.setIndeterminate(false); - mDialog.setMax(100); - mDialog.setProgress(0); - mDialog.show(); - } - - public void dismiss() { - mDialog.dismiss(); - } - - public void setTitle(String text) { - mContext.runOnUiThread(() -> { - mDialog.setTitle(text); - }); - } - - public void setStatusText(String text) { - mContext.runOnUiThread(() -> { - mDialog.setMessage(text); - }); - } - - public void setProgressRange(int range) { - mContext.runOnUiThread(() -> { - mDialog.setMax(range); - }); - } - - public void setProgressValue(int value) { - mContext.runOnUiThread(() -> { - mDialog.setProgress(value); - }); - } - - public void modalError(String message) { - Object lock = new Object(); - mContext.runOnUiThread(() -> { - new AlertDialog.Builder(mContext) - .setTitle("Error") - .setMessage(message) - .setPositiveButton(mContext.getString(R.string.android_progress_callback_ok), (dialog, button) -> { - dialog.dismiss(); - }) - .setOnDismissListener((dialogInterface) -> { - synchronized (lock) { - lock.notify(); - } - }) - .create() - .show(); - }); - - synchronized (lock) { - try { - lock.wait(); - } catch (InterruptedException e) { - } - } - } - - public void modalInformation(String message) { - Object lock = new Object(); - mContext.runOnUiThread(() -> { - new AlertDialog.Builder(mContext) - .setTitle(mContext.getString(R.string.android_progress_callback_information)) - .setMessage(message) - .setPositiveButton(mContext.getString(R.string.android_progress_callback_ok), (dialog, button) -> { - dialog.dismiss(); - }) - .setOnDismissListener((dialogInterface) -> { - synchronized (lock) { - lock.notify(); - } - }) - .create() - .show(); - }); - - synchronized (lock) { - try { - lock.wait(); - } catch (InterruptedException e) { - } - } - } - - private class ConfirmationResult { - public boolean result = false; - } - - public boolean modalConfirmation(String message) { - ConfirmationResult result = new ConfirmationResult(); - mContext.runOnUiThread(() -> { - new AlertDialog.Builder(mContext) - .setTitle(mContext.getString(R.string.android_progress_callback_confirmation)) - .setMessage(message) - .setPositiveButton(mContext.getString(R.string.android_progress_callback_yes), (dialog, button) -> { - result.result = true; - dialog.dismiss(); - }) - .setNegativeButton(mContext.getString(R.string.android_progress_callback_no), (dialog, button) -> { - result.result = false; - dialog.dismiss(); - }) - .setOnDismissListener((dialogInterface) -> { - synchronized (result) { - result.notify(); - } - }) - .create() - .show(); - }); - - synchronized (result) { - try { - result.wait(); - } catch (InterruptedException e) { - } - } - - return result.result; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ConsoleRegion.java b/android/app/src/main/java/com/github/stenzek/duckstation/ConsoleRegion.java deleted file mode 100644 index 0f56417a1..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ConsoleRegion.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.stenzek.duckstation; - -public enum ConsoleRegion { - AutoDetect, - NTSC_J, - NTSC_U, - PAL -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerAutoMapper.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerAutoMapper.java deleted file mode 100644 index 175fb3cb3..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerAutoMapper.java +++ /dev/null @@ -1,295 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Vibrator; -import android.text.InputType; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.widget.EditText; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; -import androidx.preference.PreferenceManager; - -import java.util.ArrayList; -import java.util.List; - -public class ControllerAutoMapper { - public interface CompleteCallback { - public void onComplete(); - } - - private final Context context; - private final int port; - private final CompleteCallback completeCallback; - - private InputDevice device; - private SharedPreferences prefs; - private SharedPreferences.Editor editor; - private StringBuilder log; - private String keyBase; - private String controllerType; - - public ControllerAutoMapper(Context context, int port, CompleteCallback completeCallback) { - this.context = context; - this.port = port; - this.completeCallback = completeCallback; - } - - private void log(String format, Object... args) { - log.append(String.format(format, args)); - log.append('\n'); - } - - private void setButtonBindingToKeyCode(String buttonName, int keyCode) { - log("Binding button '%s' to key '%s' (%d)", buttonName, KeyEvent.keyCodeToString(keyCode), keyCode); - - final String key = String.format("%sButton%s", keyBase, buttonName); - final String value = String.format("%s/Button%d", device.getDescriptor(), keyCode); - editor.putString(key, value); - } - - private void setButtonBindingToAxis(String buttonName, int axis, int direction) { - final char directionIndicator = (direction < 0) ? '-' : '+'; - log("Binding button '%s' to axis '%s' (%d) direction %c", buttonName, MotionEvent.axisToString(axis), axis, directionIndicator); - - final String key = String.format("%sButton%s", keyBase, buttonName); - final String value = String.format("%s/%cAxis%d", device.getDescriptor(), directionIndicator, axis); - editor.putString(key, value); - } - - private void setAxisBindingToAxis(String axisName, int axis) { - log("Binding axis '%s' to axis '%s' (%d)", axisName, MotionEvent.axisToString(axis), axis); - - final String key = String.format("%sAxis%s", keyBase, axisName); - final String value = String.format("%s/Axis%d", device.getDescriptor(), axis); - editor.putString(key, value); - } - - private void doAutoBindingButton(String buttonName, int[] keyCodes, int[][] axisCodes) { - // Prefer the axis codes, as it dispatches to that first. - if (axisCodes != null) { - final List motionRangeList = device.getMotionRanges(); - for (int[] axisAndDirection : axisCodes) { - final int axis = axisAndDirection[0]; - final int direction = axisAndDirection[1]; - for (InputDevice.MotionRange range : motionRangeList) { - if (range.getAxis() == axis) { - setButtonBindingToAxis(buttonName, axis, direction); - return; - } - } - } - } - - if (keyCodes != null) { - final boolean[] keysPresent = device.hasKeys(keyCodes); - for (int i = 0; i < keysPresent.length; i++) { - if (keysPresent[i]) { - setButtonBindingToKeyCode(buttonName, keyCodes[i]); - return; - } - } - } - - log("No automatic bindings found for button '%s'", buttonName); - } - - private void doAutoBindingAxis(String axisName, int[] axisCodes) { - // Prefer the axis codes, as it dispatches to that first. - if (axisCodes != null) { - final List motionRangeList = device.getMotionRanges(); - for (final int axis : axisCodes) { - for (InputDevice.MotionRange range : motionRangeList) { - if (range.getAxis() == axis) { - setAxisBindingToAxis(axisName, axis); - return; - } - } - } - } - - log.append(String.format("No automatic bindings found for axis '%s'\n", axisName)); - } - - public void start() { - final ArrayList deviceList = new ArrayList<>(); - for (final int deviceId : InputDevice.getDeviceIds()) { - final InputDevice inputDevice = InputDevice.getDevice(deviceId); - if (inputDevice == null || !EmulationSurfaceView.isBindableDevice(inputDevice) || - !EmulationSurfaceView.isGamepadDevice(inputDevice)) { - continue; - } - - deviceList.add(inputDevice); - } - - if (deviceList.isEmpty()) { - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.main_activity_error); - builder.setMessage(R.string.controller_auto_mapping_no_devices); - builder.setPositiveButton(R.string.main_activity_ok, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - return; - } - - final String[] deviceNames = new String[deviceList.size()]; - for (int i = 0; i < deviceList.size(); i++) - deviceNames[i] = deviceList.get(i).getName(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.controller_auto_mapping_select_device); - builder.setItems(deviceNames, (dialog, which) -> { - process(deviceList.get(which)); - }); - builder.create().show(); - } - - private void process(InputDevice device) { - this.prefs = PreferenceManager.getDefaultSharedPreferences(context); - this.editor = prefs.edit(); - this.log = new StringBuilder(); - this.device = device; - - this.keyBase = String.format("Controller%d/", port); - this.controllerType = ControllerSettingsCollectionFragment.getControllerType(prefs, port); - - setButtonBindings(); - setAxisBindings(); - setVibrationBinding(); - - this.editor.commit(); - this.editor = null; - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.controller_auto_mapping_results); - - final EditText editText = new EditText(context); - editText.setText(log.toString()); - editText.setInputType(InputType.TYPE_NULL | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - editText.setSingleLine(false); - builder.setView(editText); - - builder.setPositiveButton(R.string.main_activity_ok, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - - if (completeCallback != null) - completeCallback.onComplete(); - } - - private void setButtonBindings() { - final String[] buttonNames = AndroidHostInterface.getInstance().getControllerButtonNames(controllerType); - if (buttonNames == null || buttonNames.length == 0) { - log("No axes to bind."); - return; - } - - for (final String buttonName : buttonNames) { - switch (buttonName) { - case "Up": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_DPAD_UP}, new int[][]{{MotionEvent.AXIS_HAT_Y, -1}}); - break; - case "Down": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_DPAD_DOWN}, new int[][]{{MotionEvent.AXIS_HAT_Y, 1}}); - break; - case "Left": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_DPAD_LEFT}, new int[][]{{MotionEvent.AXIS_HAT_X, -1}}); - break; - case "Right": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_DPAD_RIGHT}, new int[][]{{MotionEvent.AXIS_HAT_X, 1}}); - break; - case "Select": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_SELECT}, null); - break; - case "Start": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_START}, null); - break; - case "Triangle": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_Y}, null); - break; - case "Cross": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_A}, null); - break; - case "Circle": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_B}, null); - break; - case "Square": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_X}, null); - break; - case "L1": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_L1}, null); - break; - case "L2": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_L2}, new int[][]{{MotionEvent.AXIS_LTRIGGER, 1}, {MotionEvent.AXIS_Z, 1}, {MotionEvent.AXIS_BRAKE, 1}}); - break; - case "R1": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_R1}, null); - break; - case "R2": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_R2}, new int[][]{{MotionEvent.AXIS_RTRIGGER, 1}, {MotionEvent.AXIS_RZ, 1}, {MotionEvent.AXIS_GAS, 1}}); - break; - case "L3": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_THUMBL}, null); - break; - case "R3": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_THUMBR}, null); - break; - case "Analog": - doAutoBindingButton(buttonName, new int[]{KeyEvent.KEYCODE_BUTTON_MODE}, null); - break; - default: - log("Button '%s' not supported by auto mapping.", buttonName); - break; - } - } - } - - private void setAxisBindings() { - final String[] axisNames = AndroidHostInterface.getInstance().getControllerAxisNames(controllerType); - if (axisNames == null || axisNames.length == 0) { - log("No axes to bind."); - return; - } - - for (final String axisName : axisNames) { - switch (axisName) { - case "LeftX": - doAutoBindingAxis(axisName, new int[]{MotionEvent.AXIS_X}); - break; - case "LeftY": - doAutoBindingAxis(axisName, new int[]{MotionEvent.AXIS_Y}); - break; - case "RightX": - doAutoBindingAxis(axisName, new int[]{MotionEvent.AXIS_RX, MotionEvent.AXIS_Z}); - break; - case "RightY": - doAutoBindingAxis(axisName, new int[]{MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ}); - break; - default: - log("Axis '%s' not supported by auto mapping.", axisName); - break; - } - } - } - - private void setVibrationBinding() { - final int motorCount = AndroidHostInterface.getInstance().getControllerVibrationMotorCount(controllerType); - if (motorCount == 0) { - log("No vibration motors to bind."); - return; - } - - final Vibrator vibrator = device.getVibrator(); - if (vibrator == null || !vibrator.hasVibrator()) { - log("Selected device has no vibrator, cannot bind vibration."); - return; - } - - log("Binding vibration to device '%s'.", device.getDescriptor()); - - final String key = String.format("%sRumble", keyBase); - editor.putString(key, device.getDescriptor()); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingDialog.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingDialog.java deleted file mode 100644 index 8b6344c25..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingDialog.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.ArraySet; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import java.util.HashMap; -import java.util.List; - -public class ControllerBindingDialog extends AlertDialog { - final static float DETECT_THRESHOLD = 0.25f; - private final ControllerBindingPreference.Type mType; - private final String mSettingKey; - private String mCurrentBinding; - private int mUpdatedAxisCode = -1; - private final HashMap mStartingAxisValues = new HashMap<>(); - - public ControllerBindingDialog(Context context, String buttonName, String settingKey, String currentBinding, ControllerBindingPreference.Type type) { - super(context); - - mType = type; - mSettingKey = settingKey; - mCurrentBinding = currentBinding; - if (mCurrentBinding == null) - mCurrentBinding = getContext().getString(R.string.controller_binding_dialog_no_binding); - - setTitle(buttonName); - updateMessage(); - setButton(BUTTON_POSITIVE, context.getString(R.string.controller_binding_dialog_cancel), (dialogInterface, button) -> dismiss()); - setButton(BUTTON_NEGATIVE, context.getString(R.string.controller_binding_dialog_clear), (dialogInterface, button) -> { - mCurrentBinding = null; - updateBinding(); - dismiss(); - }); - - setOnKeyListener(new DialogInterface.OnKeyListener() { - @Override - public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { - return onKeyDown(keyCode, event); - } - }); - } - - private void updateMessage() { - setMessage(String.format(getContext().getString(R.string.controller_binding_dialog_message), mCurrentBinding)); - } - - private void updateBinding() { - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); - if (mCurrentBinding != null) { - ArraySet values = new ArraySet<>(); - values.add(mCurrentBinding); - editor.putStringSet(mSettingKey, values); - } else { - try { - editor.remove(mSettingKey); - } catch (Exception e) { - - } - } - - editor.commit(); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - final InputDevice device = event.getDevice(); - if (!EmulationSurfaceView.isBindableDevice(device) || !EmulationSurfaceView.isBindableKeyEvent(event)) { - return super.onKeyDown(keyCode, event); - } - - if (mType == ControllerBindingPreference.Type.BUTTON || mType == ControllerBindingPreference.Type.HALF_AXIS) { - mCurrentBinding = String.format("%s/Button%d", device.getDescriptor(), event.getKeyCode()); - } else if (mType == ControllerBindingPreference.Type.VIBRATION) { - if (device.getVibrator() == null || !device.getVibrator().hasVibrator()) { - Toast.makeText(getContext(), getContext().getString(R.string.controller_settings_vibration_unsupported), Toast.LENGTH_LONG).show(); - dismiss(); - return true; - } - - mCurrentBinding = device.getDescriptor(); - } else { - return super.onKeyDown(keyCode, event); - } - - updateMessage(); - updateBinding(); - dismiss(); - return true; - } - - private void setAxisCode(InputDevice device, int axisCode, boolean positive) { - if (mUpdatedAxisCode >= 0) - return; - - mUpdatedAxisCode = axisCode; - - final int controllerIndex = 0; - if (mType == ControllerBindingPreference.Type.AXIS || mType == ControllerBindingPreference.Type.HALF_AXIS) - mCurrentBinding = String.format("%s/Axis%d", device.getDescriptor(), axisCode); - else - mCurrentBinding = String.format("%s/%cAxis%d", device.getDescriptor(), (positive) ? '+' : '-', axisCode); - - updateBinding(); - updateMessage(); - dismiss(); - } - - private boolean doAxisDetection(MotionEvent event) { - if (!EmulationSurfaceView.isBindableDevice(event.getDevice()) || !EmulationSurfaceView.isJoystickMotionEvent(event)) - return false; - - final List motionEventList = event.getDevice().getMotionRanges(); - if (motionEventList == null || motionEventList.isEmpty()) - return false; - - final int deviceId = event.getDeviceId(); - if (!mStartingAxisValues.containsKey(deviceId)) { - final float[] axisValues = new float[motionEventList.size()]; - for (int axisIndex = 0; axisIndex < motionEventList.size(); axisIndex++) { - final int axisCode = motionEventList.get(axisIndex).getAxis(); - - if (event.getHistorySize() > 0) - axisValues[axisIndex] = event.getHistoricalAxisValue(axisCode, 0); - else if (axisCode == MotionEvent.AXIS_HAT_X || axisCode == MotionEvent.AXIS_HAT_Y) - axisValues[axisIndex] = 0.0f; - else - axisValues[axisIndex] = event.getAxisValue(axisCode); - } - - mStartingAxisValues.put(deviceId, axisValues); - } - - final float[] axisValues = mStartingAxisValues.get(deviceId); - for (int axisIndex = 0; axisIndex < motionEventList.size(); axisIndex++) { - final int axisCode = motionEventList.get(axisIndex).getAxis(); - final float newValue = event.getAxisValue(axisCode); - final float delta = newValue - axisValues[axisIndex]; - if (Math.abs(delta) >= DETECT_THRESHOLD) { - setAxisCode(event.getDevice(), axisCode, delta >= 0.0f); - break; - } - } - - return true; - } - - @Override - public boolean onGenericMotionEvent(@NonNull MotionEvent event) { - if (mType != ControllerBindingPreference.Type.AXIS && - mType != ControllerBindingPreference.Type.HALF_AXIS && - mType != ControllerBindingPreference.Type.BUTTON) { - return false; - } - - if (doAxisDetection(event)) - return true; - - return super.onGenericMotionEvent(event); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java deleted file mode 100644 index 0aa4ad707..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerBindingPreference.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.AttributeSet; -import android.view.InputDevice; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.core.content.ContextCompat; -import androidx.preference.Preference; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceViewHolder; - -import java.util.Set; - -public class ControllerBindingPreference extends Preference { - public enum Type { - BUTTON, - AXIS, - HALF_AXIS, - VIBRATION - } - - private enum VisualType { - BUTTON, - AXIS, - VIBRATION, - HOTKEY - } - - private String mBindingName; - private String mDisplayName; - private String mValue; - private TextView mValueView; - private Type mType = Type.BUTTON; - private VisualType mVisualType = VisualType.BUTTON; - - private static int getIconForButton(String buttonName) { - if (buttonName.equals("Up")) { - return R.drawable.ic_controller_up_button_pressed; - } else if (buttonName.equals("Right")) { - return R.drawable.ic_controller_right_button_pressed; - } else if (buttonName.equals("Down")) { - return R.drawable.ic_controller_down_button_pressed; - } else if (buttonName.equals("Left")) { - return R.drawable.ic_controller_left_button_pressed; - } else if (buttonName.equals("Triangle")) { - return R.drawable.ic_controller_triangle_button_pressed; - } else if (buttonName.equals("Circle")) { - return R.drawable.ic_controller_circle_button_pressed; - } else if (buttonName.equals("Cross")) { - return R.drawable.ic_controller_cross_button_pressed; - } else if (buttonName.equals("Square")) { - return R.drawable.ic_controller_square_button_pressed; - } else if (buttonName.equals("Start")) { - return R.drawable.ic_controller_start_button_pressed; - } else if (buttonName.equals("Select")) { - return R.drawable.ic_controller_select_button_pressed; - } else if (buttonName.equals("L1")) { - return R.drawable.ic_controller_l1_button_pressed; - } else if (buttonName.equals("L2")) { - return R.drawable.ic_controller_l2_button_pressed; - } else if (buttonName.equals("R1")) { - return R.drawable.ic_controller_r1_button_pressed; - } else if (buttonName.equals("R2")) { - return R.drawable.ic_controller_r2_button_pressed; - } - - return R.drawable.ic_baseline_radio_button_unchecked_24; - } - - private static int getIconForAxis(String axisName) { - return R.drawable.ic_baseline_radio_button_checked_24; - } - - private static int getIconForHotkey(String hotkeyDisplayName) { - switch (hotkeyDisplayName) { - case "FastForward": - case "ToggleFastForward": - case "Turbo": - case "ToggleTurbo": - return R.drawable.ic_controller_fast_forward; - - default: - return R.drawable.ic_baseline_category_24; - } - } - - public ControllerBindingPreference(Context context, AttributeSet attrs) { - this(context, attrs, 0); - setWidgetLayoutResource(R.layout.layout_controller_binding_preference); - setIconSpaceReserved(false); - } - - public ControllerBindingPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setWidgetLayoutResource(R.layout.layout_controller_binding_preference); - setIconSpaceReserved(false); - } - - public ControllerBindingPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setWidgetLayoutResource(R.layout.layout_controller_binding_preference); - setIconSpaceReserved(false); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - - ImageView iconView = ((ImageView) holder.findViewById(R.id.controller_binding_icon)); - TextView nameView = ((TextView) holder.findViewById(R.id.controller_binding_name)); - mValueView = ((TextView) holder.findViewById(R.id.controller_binding_value)); - - int drawableId = R.drawable.ic_baseline_radio_button_checked_24; - switch (mVisualType) { - case BUTTON: - drawableId = getIconForButton(mBindingName); - break; - case AXIS: - drawableId = getIconForAxis(mBindingName); - break; - case HOTKEY: - drawableId = getIconForHotkey(mBindingName); - break; - case VIBRATION: - drawableId = R.drawable.ic_baseline_vibration_24; - break; - } - - iconView.setImageDrawable(ContextCompat.getDrawable(getContext(), drawableId)); - nameView.setText(mDisplayName); - updateValue(); - } - - @Override - protected void onClick() { - ControllerBindingDialog dialog = new ControllerBindingDialog(getContext(), mBindingName, getKey(), mValue, mType); - dialog.setOnDismissListener((dismissedDialog) -> updateValue()); - dialog.show(); - } - - public void initButton(int controllerIndex, String buttonName) { - mBindingName = buttonName; - mDisplayName = buttonName; - mType = Type.BUTTON; - mVisualType = VisualType.BUTTON; - setKey(String.format("Controller%d/Button%s", controllerIndex, buttonName)); - updateValue(); - } - - public void initAxis(int controllerIndex, String axisName, int axisType) { - mBindingName = axisName; - mDisplayName = axisName; - mType = (axisType == AndroidHostInterface.CONTROLLER_AXIS_TYPE_HALF) ? Type.HALF_AXIS : Type.AXIS; - mVisualType = VisualType.AXIS; - setKey(String.format("Controller%d/Axis%s", controllerIndex, axisName)); - updateValue(); - } - - public void initVibration(int controllerIndex) { - mBindingName = "Rumble"; - mDisplayName = getContext().getString(R.string.controller_binding_device_for_vibration); - mType = Type.VIBRATION; - mVisualType = VisualType.VIBRATION; - setKey(String.format("Controller%d/Rumble", controllerIndex)); - updateValue(); - } - - public void initHotkey(HotkeyInfo hotkeyInfo) { - mBindingName = hotkeyInfo.getName(); - mDisplayName = hotkeyInfo.getDisplayName(); - mType = Type.BUTTON; - mVisualType = VisualType.HOTKEY; - setKey(hotkeyInfo.getBindingConfigKey()); - updateValue(); - } - - public void initAutoFireButton(int controllerIndex, int autoFireSlot) { - mBindingName = String.format("AutoFire%d", autoFireSlot); - mDisplayName = getContext().getString(R.string.controller_binding_auto_fire_n, autoFireSlot); - mType = Type.BUTTON; - mVisualType = VisualType.BUTTON; - setKey(String.format("Controller%d/AutoFire%d", controllerIndex, autoFireSlot)); - updateValue(); - } - - private String prettyPrintBinding(String value) { - final int index = value.indexOf('/'); - String device, binding; - if (index >= 0) { - device = value.substring(0, index); - binding = value.substring(index); - } else { - device = value; - binding = ""; - } - - String humanName = device; - int deviceIndex = -1; - - final int[] deviceIds = InputDevice.getDeviceIds(); - for (int i = 0; i < deviceIds.length; i++) { - final InputDevice inputDevice = InputDevice.getDevice(deviceIds[i]); - if (inputDevice == null || !inputDevice.getDescriptor().equals(device)) { - continue; - } - - humanName = inputDevice.getName(); - deviceIndex = i; - break; - } - - final int MAX_LENGTH = 40; - if (humanName.length() > MAX_LENGTH) { - final StringBuilder shortenedName = new StringBuilder(); - shortenedName.append(humanName, 0, MAX_LENGTH / 2); - shortenedName.append("..."); - shortenedName.append(humanName, humanName.length() - (MAX_LENGTH / 2), - humanName.length()); - - humanName = shortenedName.toString(); - } - - if (deviceIndex < 0) - return String.format("%s[??]%s", humanName, binding); - else - return String.format("%s[%d]%s", humanName, deviceIndex, binding); - } - - private void updateValue(String value) { - mValue = value; - if (mValueView != null) { - if (value != null) - mValueView.setText(value); - else - mValueView.setText(getContext().getString(R.string.controller_binding_dialog_no_binding)); - } - } - - public void updateValue() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - Set values = PreferenceHelpers.getStringSet(prefs, getKey()); - if (values != null) { - StringBuilder sb = new StringBuilder(); - for (String value : values) { - if (sb.length() > 0) - sb.append(", "); - sb.append(prettyPrintBinding(value)); - } - - updateValue(sb.toString()); - } else { - updateValue(null); - } - } - - public void clearBinding(SharedPreferences.Editor prefEditor) { - try { - prefEditor.remove(getKey()); - } catch (Exception e) { - - } - - updateValue(null); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsActivity.java deleted file mode 100644 index 73448dbc8..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsActivity.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.EditText; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.preference.PreferenceManager; - -import java.util.ArrayList; - -public class ControllerSettingsActivity extends AppCompatActivity { - private ControllerSettingsCollectionFragment fragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - setContentView(R.layout.settings_activity); - - fragment = new ControllerSettingsCollectionFragment(); - fragment.setMultitapModeChangedListener(this::recreate); - - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings, fragment) - .commit(); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(R.string.controller_mapping_activity_title); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_controller_mapping, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - final int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_load_profile) { - doLoadProfile(); - return true; - } else if (id == R.id.action_save_profile) { - doSaveProfile(); - return true; - } else if (id == R.id.action_clear_bindings) { - fragment.clearAllBindings(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void displayError(String text) { - new AlertDialog.Builder(this) - .setTitle(R.string.emulation_activity_error) - .setMessage(text) - .setNegativeButton(R.string.main_activity_ok, ((dialog, which) -> dialog.dismiss())) - .create() - .show(); - } - - private void doLoadProfile() { - final String[] profileNames = AndroidHostInterface.getInstance().getInputProfileNames(); - if (profileNames == null) { - displayError(getString(R.string.controller_mapping_activity_no_profiles_found)); - return; - } - - new AlertDialog.Builder(this) - .setTitle(R.string.controller_mapping_activity_select_input_profile) - .setItems(profileNames, (dialog, choice) -> { - doLoadProfile(profileNames[choice]); - dialog.dismiss(); - }) - .setNegativeButton(R.string.controller_mapping_activity_cancel, ((dialog, which) -> dialog.dismiss())) - .create() - .show(); - } - - private void doLoadProfile(String profileName) { - if (!AndroidHostInterface.getInstance().loadInputProfile(profileName)) { - displayError(String.format(getString(R.string.controller_mapping_activity_failed_to_load_profile), profileName)); - return; - } - - fragment.updateAllBindings(); - } - - private void doSaveProfile() { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - final EditText input = new EditText(this); - builder.setTitle(R.string.controller_mapping_activity_input_profile_name); - builder.setView(input); - builder.setPositiveButton(R.string.controller_mapping_activity_save, (dialog, which) -> { - final String name = input.getText().toString(); - if (name.isEmpty()) { - displayError(getString(R.string.controller_mapping_activity_name_must_be_provided)); - return; - } - - if (!AndroidHostInterface.getInstance().saveInputProfile(name)) { - displayError(getString(R.string.controller_mapping_activity_failed_to_save_input_profile)); - return; - } - - Toast.makeText(ControllerSettingsActivity.this, String.format(ControllerSettingsActivity.this.getString(R.string.controller_mapping_activity_input_profile_saved), name), - Toast.LENGTH_LONG).show(); - }); - builder.setNegativeButton(R.string.controller_mapping_activity_cancel, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java deleted file mode 100644 index 00b86191d..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ControllerSettingsCollectionFragment.java +++ /dev/null @@ -1,459 +0,0 @@ -package com.github.stenzek.duckstation; - - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.preference.SeekBarPreference; -import androidx.preference.SwitchPreferenceCompat; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; - -import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; - -import java.util.ArrayList; -import java.util.HashMap; - -public class ControllerSettingsCollectionFragment extends Fragment { - public static final String MULTITAP_MODE_SETTINGS_KEY = "ControllerPorts/MultitapMode"; - private static final int NUM_MAIN_CONTROLLER_PORTS = 2; - private static final int NUM_SUB_CONTROLLER_PORTS = 4; - private static final char[] SUB_CONTROLLER_PORT_NAMES = new char[]{'A', 'B', 'C', 'D'}; - private static final int NUM_AUTO_FIRE_BUTTONS = 4; - - public interface MultitapModeChangedListener { - void onChanged(); - } - - private final ArrayList preferences = new ArrayList<>(); - private SettingsCollectionAdapter adapter; - private ViewPager2 viewPager; - private String[] controllerPortNames; - - private MultitapModeChangedListener multitapModeChangedListener; - - public ControllerSettingsCollectionFragment() { - } - - public static String getControllerTypeKey(int port) { - return String.format("Controller%d/Type", port); - } - - public static String getControllerType(SharedPreferences prefs, int port) { - final String defaultControllerType = (port == 1) ? "DigitalController" : "None"; - return prefs.getString(getControllerTypeKey(port), defaultControllerType); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_controller_settings, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - final String multitapMode = PreferenceManager.getDefaultSharedPreferences(getContext()).getString( - MULTITAP_MODE_SETTINGS_KEY, "Disabled"); - - final ArrayList portNames = new ArrayList<>(); - for (int i = 0; i < NUM_MAIN_CONTROLLER_PORTS; i++) { - final boolean isMultitap = (multitapMode.equals("BothPorts") || - (i == 0 && multitapMode.equals("Port1Only")) || - (i == 1 && multitapMode.equals("Port2Only"))); - - if (isMultitap) { - for (int j = 0; j < NUM_SUB_CONTROLLER_PORTS; j++) { - portNames.add(getContext().getString( - R.string.controller_settings_sub_port_format, - i + 1, SUB_CONTROLLER_PORT_NAMES[j])); - } - } else { - portNames.add(getContext().getString( - R.string.controller_settings_main_port_format, - i + 1)); - } - } - - controllerPortNames = new String[portNames.size()]; - portNames.toArray(controllerPortNames); - - adapter = new SettingsCollectionAdapter(this, controllerPortNames.length); - viewPager = view.findViewById(R.id.view_pager); - viewPager.setAdapter(adapter); - - TabLayout tabLayout = view.findViewById(R.id.tab_layout); - new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { - if (position == 0) - tab.setText(R.string.controller_settings_tab_settings); - else if (position <= controllerPortNames.length) - tab.setText(controllerPortNames[position - 1]); - else - tab.setText(R.string.controller_settings_tab_hotkeys); - }).attach(); - } - - public void setMultitapModeChangedListener(MultitapModeChangedListener multitapModeChangedListener) { - this.multitapModeChangedListener = multitapModeChangedListener; - } - - public void clearAllBindings() { - SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit(); - for (ControllerBindingPreference pref : preferences) - pref.clearBinding(prefEdit); - prefEdit.commit(); - } - - public void updateAllBindings() { - for (ControllerBindingPreference pref : preferences) - pref.updateValue(); - } - - public static class SettingsFragment extends PreferenceFragmentCompat { - private final ControllerSettingsCollectionFragment parent; - - public SettingsFragment(ControllerSettingsCollectionFragment parent) { - this.parent = parent; - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.controllers_preferences, rootKey); - - final Preference multitapModePreference = getPreferenceScreen().findPreference(MULTITAP_MODE_SETTINGS_KEY); - if (multitapModePreference != null) { - multitapModePreference.setOnPreferenceChangeListener((pref, newValue) -> { - if (parent.multitapModeChangedListener != null) - parent.multitapModeChangedListener.onChanged(); - - return true; - }); - } - } - } - - public static class ControllerPortFragment extends PreferenceFragmentCompat { - private final ControllerSettingsCollectionFragment parent; - private final int controllerIndex; - private PreferenceCategory mButtonsCategory; - private PreferenceCategory mAxisCategory; - private PreferenceCategory mSettingsCategory; - private PreferenceCategory mAutoFireCategory; - private PreferenceCategory mAutoFireBindingsCategory; - - public ControllerPortFragment(ControllerSettingsCollectionFragment parent, int controllerIndex) { - this.parent = parent; - this.controllerIndex = controllerIndex; - } - - private static void clearBindingsInCategory(SharedPreferences.Editor editor, PreferenceCategory category) { - for (int i = 0; i < category.getPreferenceCount(); i++) { - final Preference preference = category.getPreference(i); - if (preference instanceof ControllerBindingPreference) - ((ControllerBindingPreference) preference).clearBinding(editor); - } - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext()); - setPreferenceScreen(ps); - createPreferences(); - } - - private SwitchPreferenceCompat createTogglePreference(String key, int title, int summary, boolean defaultValue) { - final SwitchPreferenceCompat pref = new SwitchPreferenceCompat(getContext()); - pref.setKey(key); - pref.setTitle(title); - pref.setSummary(summary); - pref.setIconSpaceReserved(false); - pref.setDefaultValue(defaultValue); - return pref; - } - - private String getAutoToggleSummary(SharedPreferences sp, int slot) { - final String button = sp.getString(String.format("AutoFire%dButton", slot), null); - if (button == null || button.length() == 0) - return "Not Configured"; - - return String.format("%s every %d frames", button, sp.getInt("AutoFire%dFrequency", 2)); - } - - private void createPreferences() { - final PreferenceScreen ps = getPreferenceScreen(); - final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); - final String controllerType = getControllerType(sp, controllerIndex); - final String[] controllerButtons = AndroidHostInterface.getControllerButtonNames(controllerType); - final String[] axisButtons = AndroidHostInterface.getControllerAxisNames(controllerType); - final int vibrationMotors = AndroidHostInterface.getControllerVibrationMotorCount(controllerType); - - final ListPreference typePreference = new ListPreference(getContext()); - typePreference.setEntries(R.array.settings_controller_type_entries); - typePreference.setEntryValues(R.array.settings_controller_type_values); - typePreference.setKey(getControllerTypeKey(controllerIndex)); - typePreference.setValue(controllerType); - typePreference.setTitle(R.string.settings_controller_type); - typePreference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance()); - typePreference.setIconSpaceReserved(false); - typePreference.setOnPreferenceChangeListener((pref, value) -> { - removePreferences(); - createPreferences(value.toString()); - return true; - }); - ps.addPreference(typePreference); - - final Preference autoBindPreference = new Preference(getContext()); - autoBindPreference.setTitle(R.string.controller_settings_automatic_mapping); - autoBindPreference.setSummary(R.string.controller_settings_summary_automatic_mapping); - autoBindPreference.setIconSpaceReserved(false); - autoBindPreference.setOnPreferenceClickListener(preference -> { - final ControllerAutoMapper mapper = new ControllerAutoMapper(getContext(), controllerIndex, () -> { - removePreferences(); - createPreferences(typePreference.getValue()); - }); - mapper.start(); - return true; - }); - ps.addPreference(autoBindPreference); - - final Preference clearBindingsPreference = new Preference(getContext()); - clearBindingsPreference.setTitle(R.string.controller_settings_clear_controller_bindings); - clearBindingsPreference.setSummary(R.string.controller_settings_summary_clear_controller_bindings); - clearBindingsPreference.setIconSpaceReserved(false); - clearBindingsPreference.setOnPreferenceClickListener(preference -> { - final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setMessage(R.string.controller_settings_clear_controller_bindings_confirm); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - dialog.dismiss(); - clearBindings(); - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - return true; - }); - ps.addPreference(clearBindingsPreference); - - mButtonsCategory = new PreferenceCategory(getContext()); - mButtonsCategory.setTitle(R.string.controller_settings_category_button_bindings); - mButtonsCategory.setIconSpaceReserved(false); - ps.addPreference(mButtonsCategory); - - mAxisCategory = new PreferenceCategory(getContext()); - mAxisCategory.setTitle(R.string.controller_settings_category_axis_bindings); - mAxisCategory.setIconSpaceReserved(false); - ps.addPreference(mAxisCategory); - - mSettingsCategory = new PreferenceCategory(getContext()); - mSettingsCategory.setTitle(R.string.controller_settings_category_settings); - mSettingsCategory.setIconSpaceReserved(false); - ps.addPreference(mSettingsCategory); - - mAutoFireCategory = new PreferenceCategory(getContext()); - mAutoFireCategory.setTitle(R.string.controller_settings_category_auto_fire_buttons); - mAutoFireCategory.setIconSpaceReserved(false); - ps.addPreference(mAutoFireCategory); - - mAutoFireBindingsCategory = new PreferenceCategory(getContext()); - mAutoFireBindingsCategory.setTitle(R.string.controller_settings_category_auto_fire_bindings); - mAutoFireBindingsCategory.setIconSpaceReserved(false); - ps.addPreference(mAutoFireBindingsCategory); - - createPreferences(controllerType); - } - - @SuppressLint("DefaultLocale") - private void createPreferences(String controllerType) { - final PreferenceScreen ps = getPreferenceScreen(); - final SharedPreferences sp = getPreferenceManager().getSharedPreferences(); - final String[] buttonNames = AndroidHostInterface.getControllerButtonNames(controllerType); - final String[] axisNames = AndroidHostInterface.getControllerAxisNames(controllerType); - final int vibrationMotors = AndroidHostInterface.getControllerVibrationMotorCount(controllerType); - - if (buttonNames != null) { - for (String buttonName : buttonNames) { - final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null); - cbp.initButton(controllerIndex, buttonName); - mButtonsCategory.addPreference(cbp); - parent.preferences.add(cbp); - } - } - - if (axisNames != null) { - for (String axisName : axisNames) { - final int axisType = AndroidHostInterface.getControllerAxisType(controllerType, axisName); - final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null); - cbp.initAxis(controllerIndex, axisName, axisType); - mAxisCategory.addPreference(cbp); - parent.preferences.add(cbp); - } - } - - if (vibrationMotors > 0) { - final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null); - cbp.initVibration(controllerIndex); - mSettingsCategory.addPreference(cbp); - parent.preferences.add(cbp); - } - - if (controllerType.equals("AnalogController")) { - mSettingsCategory.addPreference( - createTogglePreference(String.format("Controller%d/ForceAnalogOnReset", controllerIndex), - R.string.settings_enable_analog_mode_on_reset, R.string.settings_summary_enable_analog_mode_on_reset, true)); - - mSettingsCategory.addPreference( - createTogglePreference(String.format("Controller%d/AnalogDPadInDigitalMode", controllerIndex), - R.string.settings_use_analog_sticks_for_dpad, R.string.settings_summary_use_analog_sticks_for_dpad, true)); - } - - if (buttonNames != null) { - for (int autoFireSlot = 1; autoFireSlot <= NUM_AUTO_FIRE_BUTTONS; autoFireSlot++) { - final ListPreference autoFirePreference = new ListPreference(getContext()); - autoFirePreference.setEntries(buttonNames); - autoFirePreference.setEntryValues(buttonNames); - autoFirePreference.setKey(String.format("Controller%d/AutoFire%dButton", controllerIndex, autoFireSlot)); - autoFirePreference.setTitle(getContext().getString(R.string.controller_settings_auto_fire_n_button, autoFireSlot)); - autoFirePreference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance()); - autoFirePreference.setIconSpaceReserved(false); - mAutoFireCategory.addPreference(autoFirePreference); - - final SeekBarPreference frequencyPreference = new SeekBarPreference(getContext()); - frequencyPreference.setMin(1); - frequencyPreference.setMax(60); - frequencyPreference.setKey(String.format("Controller%d/AutoFire%dFrequency", controllerIndex, autoFireSlot)); - frequencyPreference.setDefaultValue(2); - frequencyPreference.setTitle(getContext().getString(R.string.controller_settings_auto_fire_n_frequency, autoFireSlot)); - frequencyPreference.setIconSpaceReserved(false); - frequencyPreference.setShowSeekBarValue(true); - mAutoFireCategory.addPreference(frequencyPreference); - } - - for (int autoFireSlot = 1; autoFireSlot <= NUM_AUTO_FIRE_BUTTONS; autoFireSlot++) { - final ControllerBindingPreference bindingPreference = new ControllerBindingPreference(getContext(), null); - bindingPreference.initAutoFireButton(controllerIndex, autoFireSlot); - mAutoFireBindingsCategory.addPreference(bindingPreference); - } - } - } - - private void removePreferences() { - for (int i = 0; i < mButtonsCategory.getPreferenceCount(); i++) { - parent.preferences.remove(mButtonsCategory.getPreference(i)); - } - mButtonsCategory.removeAll(); - - for (int i = 0; i < mAxisCategory.getPreferenceCount(); i++) { - parent.preferences.remove(mAxisCategory.getPreference(i)); - } - mAxisCategory.removeAll(); - - for (int i = 0; i < mSettingsCategory.getPreferenceCount(); i++) { - parent.preferences.remove(mSettingsCategory.getPreference(i)); - } - mSettingsCategory.removeAll(); - - for (int i = 0; i < mAutoFireCategory.getPreferenceCount(); i++) { - parent.preferences.remove(mAutoFireCategory.getPreference(i)); - } - mAutoFireCategory.removeAll(); - - for (int i = 0; i < mAutoFireBindingsCategory.getPreferenceCount(); i++) { - parent.preferences.remove(mAutoFireBindingsCategory.getPreference(i)); - } - mAutoFireBindingsCategory.removeAll(); - } - - private void clearBindings() { - final SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit(); - clearBindingsInCategory(editor, mButtonsCategory); - clearBindingsInCategory(editor, mAxisCategory); - clearBindingsInCategory(editor, mSettingsCategory); - editor.commit(); - - Toast.makeText(parent.getContext(), parent.getString( - R.string.controller_settings_clear_controller_bindings_done, controllerIndex), - Toast.LENGTH_LONG).show(); - } - } - - public static class HotkeyFragment extends PreferenceFragmentCompat { - private final ControllerSettingsCollectionFragment parent; - private final HotkeyInfo[] mHotkeyInfo; - - public HotkeyFragment(ControllerSettingsCollectionFragment parent) { - this.parent = parent; - this.mHotkeyInfo = AndroidHostInterface.getInstance().getHotkeyInfoList(); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext()); - if (mHotkeyInfo != null) { - final HashMap categoryMap = new HashMap<>(); - - for (HotkeyInfo hotkeyInfo : mHotkeyInfo) { - PreferenceCategory category = categoryMap.containsKey(hotkeyInfo.getCategory()) ? - categoryMap.get(hotkeyInfo.getCategory()) : null; - if (category == null) { - category = new PreferenceCategory(getContext()); - category.setTitle(hotkeyInfo.getCategory()); - category.setIconSpaceReserved(false); - categoryMap.put(hotkeyInfo.getCategory(), category); - ps.addPreference(category); - } - - final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null); - cbp.initHotkey(hotkeyInfo); - category.addPreference(cbp); - parent.preferences.add(cbp); - } - } - - setPreferenceScreen(ps); - } - } - - public static class SettingsCollectionAdapter extends FragmentStateAdapter { - private final ControllerSettingsCollectionFragment parent; - private final int controllerPorts; - - public SettingsCollectionAdapter(@NonNull ControllerSettingsCollectionFragment parent, int controllerPorts) { - super(parent); - this.parent = parent; - this.controllerPorts = controllerPorts; - } - - @NonNull - @Override - public Fragment createFragment(int position) { - if (position == 0) - return new SettingsFragment(parent); - else if (position <= controllerPorts) - return new ControllerPortFragment(parent, position); - else - return new HotkeyFragment(parent); - } - - @Override - public int getItemCount() { - return controllerPorts + 2; - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/DiscRegion.java b/android/app/src/main/java/com/github/stenzek/duckstation/DiscRegion.java deleted file mode 100644 index 813d9e717..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/DiscRegion.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.stenzek.duckstation; - -public enum DiscRegion { - NTSC_J, - NTSC_U, - PAL, - Other -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmptyGameListFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmptyGameListFragment.java deleted file mode 100644 index 793cf2d7d..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmptyGameListFragment.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -public class EmptyGameListFragment extends Fragment { - private static final String SUPPORTED_FORMATS_STRING = - ".cue (Cue Sheets)\n" + - ".iso/.img (Single Track Image)\n" + - ".ecm (Error Code Modeling Image)\n" + - ".mds (Media Descriptor Sidecar)\n" + - ".chd (Compressed Hunks of Data)\n" + - ".pbp (PlayStation Portable, Only Decrypted)"; - - private MainActivity parent; - - public EmptyGameListFragment(MainActivity parent) { - super(R.layout.fragment_empty_game_list); - this.parent = parent; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - ((TextView) view.findViewById(R.id.supported_formats)).setText( - getString(R.string.main_activity_empty_game_list_supported_formats, SUPPORTED_FORMATS_STRING)); - ((Button) view.findViewById(R.id.add_game_directory)).setOnClickListener(v -> parent.startAddGameDirectory()); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java deleted file mode 100644 index 8192df706..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ /dev/null @@ -1,988 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.hardware.input.InputManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Vibrator; -import android.util.Log; -import android.view.Display; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SurfaceHolder; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; - -/** - * An example full-screen activity that shows and hides the system UI (i.e. - * status bar and navigation/system bar) with user interaction. - */ -public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback { - /** - * Settings interfaces. - */ - private SharedPreferences mPreferences; - private boolean mWasDestroyed = false; - private boolean mStopRequested = false; - private boolean mApplySettingsOnSurfaceRestored = false; - private String mGamePath = null; - private String mGameCode = null; - private String mGameTitle = null; - private String mGameCoverPath = null; - private EmulationSurfaceView mContentView; - private MenuDialogFragment mPauseMenu; - - private boolean getBooleanSetting(String key, boolean defaultValue) { - return mPreferences.getBoolean(key, defaultValue); - } - - private void setBooleanSetting(String key, boolean value) { - SharedPreferences.Editor editor = mPreferences.edit(); - editor.putBoolean(key, value); - editor.apply(); - } - - private String getStringSetting(String key, String defaultValue) { - return mPreferences.getString(key, defaultValue); - } - - private int getIntSetting(String key, int defaultValue) { - try { - return mPreferences.getInt(key, defaultValue); - } catch (ClassCastException e) { - try { - final String stringValue = mPreferences.getString(key, Integer.toString(defaultValue)); - return Integer.parseInt(stringValue); - } catch (Exception e2) { - return defaultValue; - } - } - } - - private void setStringSetting(String key, String value) { - SharedPreferences.Editor editor = mPreferences.edit(); - editor.putString(key, value); - editor.apply(); - } - - private void reportErrorOnUIThread(String message) { - // Toast.makeText(this, message, Toast.LENGTH_LONG); - new AlertDialog.Builder(this) - .setTitle(R.string.emulation_activity_error) - .setMessage(message) - .setPositiveButton(R.string.emulation_activity_ok, (dialog, button) -> { - dialog.dismiss(); - enableFullscreenImmersive(); - }) - .create() - .show(); - } - - public void reportError(String message) { - Log.e("EmulationActivity", message); - - Object lock = new Object(); - runOnUiThread(() -> { - // Toast.makeText(this, message, Toast.LENGTH_LONG); - new AlertDialog.Builder(this) - .setTitle(R.string.emulation_activity_error) - .setMessage(message) - .setPositiveButton(R.string.emulation_activity_ok, (dialog, button) -> { - dialog.dismiss(); - enableFullscreenImmersive(); - synchronized (lock) { - lock.notify(); - } - }) - .create() - .show(); - }); - - synchronized (lock) { - try { - lock.wait(); - } catch (InterruptedException e) { - } - } - } - - private EmulationThread mEmulationThread; - - private void stopEmulationThread() { - if (mEmulationThread == null) - return; - - mEmulationThread.stopAndJoin(); - mEmulationThread = null; - } - - public void onEmulationStarted() { - runOnUiThread(() -> { - updateRequestedOrientation(); - updateOrientation(); - }); - } - - public void onEmulationStopped() { - runOnUiThread(() -> { - if (!mWasDestroyed && !mStopRequested) - finish(); - }); - } - - public void onRunningGameChanged(String path, String code, String title, String coverPath) { - runOnUiThread(() -> { - mGamePath = path; - mGameTitle = title; - mGameCode = code; - mGameCoverPath = coverPath; - }); - } - - public float getRefreshRate() { - WindowManager windowManager = getWindowManager(); - if (windowManager == null) { - windowManager = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)); - if (windowManager == null) - return -1.0f; - } - - Display display = windowManager.getDefaultDisplay(); - if (display == null) - return -1.0f; - - return display.getRefreshRate(); - } - - public void openPauseMenu() { - runOnUiThread(() -> { - showPauseMenu(); - }); - } - - public String[] getInputDeviceNames() { - return (mContentView != null) ? mContentView.getInputDeviceNames() : null; - } - - public boolean hasInputDeviceVibration(int controllerIndex) { - return (mContentView != null) ? mContentView.hasInputDeviceVibration(controllerIndex) : null; - } - - public void setInputDeviceVibration(int controllerIndex, float smallMotor, float largeMotor) { - if (mContentView != null) - mContentView.setInputDeviceVibration(controllerIndex, smallMotor, largeMotor); - } - - private void doApplySettings() { - AndroidHostInterface.getInstance().applySettings(); - updateRequestedOrientation(); - updateControllers(); - updateSustainedPerformanceMode(); - updateDisplayInCutout(); - } - - private void applySettings() { - if (!AndroidHostInterface.getInstance().isEmulationThreadRunning()) - return; - - if (AndroidHostInterface.getInstance().hasSurface()) { - doApplySettings(); - } else { - mApplySettingsOnSurfaceRestored = true; - } - } - - /// Ends the activity if it was restored without properly being created. - private boolean checkActivityIsValid() { - if (!AndroidHostInterface.hasInstance() || !AndroidHostInterface.getInstance().isEmulationThreadRunning()) { - finish(); - return false; - } - - return true; - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - // Once we get a surface, we can boot. - AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height); - - if (mEmulationThread != null) { - updateOrientation(); - - if (mApplySettingsOnSurfaceRestored) { - mApplySettingsOnSurfaceRestored = false; - doApplySettings(); - } - - return; - } - - final String bootPath = getIntent().getStringExtra("bootPath"); - final boolean saveStateOnExit = getBooleanSetting("Main/SaveStateOnExit", true); - final boolean resumeState = getIntent().getBooleanExtra("resumeState", saveStateOnExit); - final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath"); - - mEmulationThread = EmulationThread.create(this, bootPath, resumeState, bootSaveStatePath); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.i("EmulationActivity", "Surface destroyed"); - - if (mPauseMenu != null) - mPauseMenu.close(false); - - // Save the resume state in case we never get back again... - if (AndroidHostInterface.getInstance().isEmulationThreadRunning() && !mStopRequested) - AndroidHostInterface.getInstance().saveResumeState(true); - - AndroidHostInterface.getInstance().surfaceChanged(null, 0, 0, 0); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - super.onCreate(savedInstanceState); - - Log.i("EmulationActivity", "OnCreate"); - - // we might be coming from a third-party launcher if the host interface isn't setup - if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { - finish(); - return; - } - - enableFullscreenImmersive(); - setContentView(R.layout.activity_emulation); - - mContentView = findViewById(R.id.fullscreen_content); - mContentView.getHolder().addCallback(this); - mContentView.setFocusableInTouchMode(true); - mContentView.setFocusable(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mContentView.setFocusedByDefault(true); - } - mContentView.requestFocus(); - - // Sort out rotation. - updateOrientation(); - updateSustainedPerformanceMode(); - updateDisplayInCutout(); - - // Hook up controller input. - updateControllers(); - registerInputDeviceListener(); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - enableFullscreenImmersive(); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - enableFullscreenImmersive(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - Log.i("EmulationActivity", "OnStop"); - if (mEmulationThread != null) { - mWasDestroyed = true; - stopEmulationThread(); - } - - unregisterInputDeviceListener(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (!checkActivityIsValid()) { - // we must've got killed off in the background :( - return; - } - - if (requestCode == REQUEST_CODE_SETTINGS) { - if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { - applySettings(); - } - } else if (requestCode == REQUEST_IMPORT_PATCH_CODES) { - if (data == null || data.getData() == null) - return; - - importPatchesFromFile(data.getData()); - } else if (requestCode == REQUEST_CHANGE_DISC_FILE) { - if (data == null || data.getData() == null) - return; - - AndroidHostInterface.getInstance().setMediaFilename(data.getDataString()); - } - } - - @Override - public void onBackPressed() { - showPauseMenu(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (mContentView.onKeyDown(event.getKeyCode(), event)) - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (mContentView.onKeyUp(event.getKeyCode(), event)) - return true; - } - - return super.dispatchKeyEvent(event); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent ev) { - if (mContentView.onGenericMotionEvent(ev)) - return true; - - return super.dispatchGenericMotionEvent(ev); - } - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - if (checkActivityIsValid()) - updateOrientation(newConfig.orientation); - } - - private void updateRequestedOrientation() { - final String orientation = getStringSetting("Main/EmulationScreenOrientation", "unspecified"); - if (orientation.equals("portrait")) - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT); - else if (orientation.equals("landscape")) - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE); - else if (orientation.equals("sensor")) - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); - else - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - - private void updateOrientation() { - final int orientation = getResources().getConfiguration().orientation; - updateOrientation(orientation); - } - - private void updateOrientation(int newOrientation) { - if (newOrientation == Configuration.ORIENTATION_PORTRAIT) - AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT); - else - AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_CENTER); - - if (mTouchscreenController != null) - mTouchscreenController.updateOrientation(); - } - - private void enableFullscreenImmersive() { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - if (mContentView != null) - mContentView.requestFocus(); - } - - private void updateDisplayInCutout() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) - return; - - final boolean shouldExpand = getBooleanSetting("Display/ExpandToCutout", false); - final boolean isExpanded = getWindow().getAttributes().layoutInDisplayCutoutMode == - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - - if (shouldExpand == isExpanded) - return; - - WindowManager.LayoutParams attribs = getWindow().getAttributes(); - attribs.layoutInDisplayCutoutMode = shouldExpand ? - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES : - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; - getWindow().setAttributes(attribs); - } - - private static final int REQUEST_CODE_SETTINGS = 0; - private static final int REQUEST_IMPORT_PATCH_CODES = 1; - private static final int REQUEST_CHANGE_DISC_FILE = 2; - - private void onMenuClosed() { - enableFullscreenImmersive(); - - if (AndroidHostInterface.getInstance().isEmulationThreadPaused()) - AndroidHostInterface.getInstance().pauseEmulationThread(false); - } - - private boolean disableDialogMenuItem(AlertDialog dialog, int index) { - final ListView listView = dialog.getListView(); - if (listView == null) - return false; - - final View childItem = listView.getChildAt(index); - if (childItem == null) - return false; - - childItem.setEnabled(false); - childItem.setClickable(false); - childItem.setOnClickListener((v) -> {}); - return true; - } - - private void showPauseMenu() { - if (!AndroidHostInterface.getInstance().isEmulationThreadPaused()) { - AndroidHostInterface.getInstance().pauseEmulationThread(true); - } - - if (mPauseMenu != null) - mPauseMenu.close(false); - - mPauseMenu = new MenuDialogFragment(this); - mPauseMenu.show(getSupportFragmentManager(), "MenuDialogFragment"); - } - - private void showSaveStateMenu(boolean saving) { - final SaveStateInfo[] infos = AndroidHostInterface.getInstance().getSaveStateInfo(true); - if (infos == null) { - onMenuClosed(); - return; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - final ListView listView = new ListView(this); - listView.setAdapter(new SaveStateInfo.ListAdapter(this, infos)); - builder.setView(listView); - builder.setOnDismissListener((dialog) -> { - onMenuClosed(); - }); - - final AlertDialog dialog = builder.create(); - - listView.setOnItemClickListener((parent, view, position, id) -> { - SaveStateInfo info = infos[position]; - if (saving) { - AndroidHostInterface.getInstance().saveState(info.isGlobal(), info.getSlot()); - } else { - AndroidHostInterface.getInstance().loadState(info.isGlobal(), info.getSlot()); - } - dialog.dismiss(); - }); - - dialog.show(); - } - - private void showTouchscreenControllerMenu() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.dialog_touchscreen_controller_settings); - builder.setItems(R.array.emulation_touchscreen_menu, (dialogInterface, i) -> { - switch (i) { - case 0: // Change Type - { - final String currentValue = getStringSetting("Controller1/TouchscreenControllerView", ""); - final String[] values = getResources().getStringArray(R.array.settings_touchscreen_controller_view_values); - int currentIndex = -1; - for (int k = 0; k < values.length; k++) { - if (currentValue.equals(values[k])) { - currentIndex = k; - break; - } - } - - final AlertDialog.Builder subBuilder = new AlertDialog.Builder(this); - subBuilder.setTitle(R.string.dialog_touchscreen_controller_type); - subBuilder.setSingleChoiceItems(R.array.settings_touchscreen_controller_view_entries, currentIndex, (dialog, j) -> { - setStringSetting("Controller1/TouchscreenControllerView", values[j]); - updateControllers(); - }); - subBuilder.setNegativeButton(R.string.dialog_done, (dialog, which) -> { - dialog.dismiss(); - }); - subBuilder.setOnDismissListener(dialog -> onMenuClosed()); - subBuilder.create().show(); - } - break; - - case 1: // Change Opacity - { - if (mTouchscreenController != null) { - AlertDialog.Builder subBuilder = mTouchscreenController.createOpacityDialog(this); - subBuilder.setOnDismissListener(dialog -> onMenuClosed()); - subBuilder.create().show(); - } else { - onMenuClosed(); - } - - } - break; - - case 2: // Add/Remove Buttons - { - if (mTouchscreenController != null) { - AlertDialog.Builder subBuilder = mTouchscreenController.createAddRemoveButtonDialog(this); - subBuilder.setOnDismissListener(dialog -> onMenuClosed()); - subBuilder.create().show(); - } else { - onMenuClosed(); - } - } - break; - - case 3: // Edit Positions - case 4: // Edit Scale - { - if (mTouchscreenController != null) { - // we deliberately don't call onMenuClosed() here to keep the system paused. - // but we need to re-enable immersive mode to get proper editing. - enableFullscreenImmersive(); - mTouchscreenController.startLayoutEditing( - (i == 4) ? TouchscreenControllerView.EditMode.SCALE : - TouchscreenControllerView.EditMode.POSITION); - } else { - // no controller - onMenuClosed(); - } - } - break; - } - }); - - builder.setOnCancelListener(dialogInterface -> onMenuClosed()); - builder.create().show(); - } - - private void showPatchesMenu() { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - - final PatchCode[] codes = AndroidHostInterface.getInstance().getPatchCodeList(); - if (codes != null) { - CharSequence[] items = new CharSequence[codes.length]; - boolean[] itemsChecked = new boolean[codes.length]; - for (int i = 0; i < codes.length; i++) { - final PatchCode cc = codes[i]; - items[i] = cc.getDisplayText(); - itemsChecked[i] = cc.isEnabled(); - } - - builder.setMultiChoiceItems(items, itemsChecked, (dialogInterface, i, checked) -> { - AndroidHostInterface.getInstance().setPatchCodeEnabled(i, checked); - }); - } - - builder.setNegativeButton(R.string.emulation_activity_ok, (dialogInterface, i) -> { - dialogInterface.dismiss(); - }); - builder.setNeutralButton(R.string.emulation_activity_import_patch_codes, (dialogInterface, i) -> { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.emulation_activity_choose_patch_code_file)), REQUEST_IMPORT_PATCH_CODES); - }); - - builder.setOnDismissListener(dialogInterface -> onMenuClosed()); - builder.create().show(); - } - - private void importPatchesFromFile(Uri uri) { - String str = FileHelper.readStringFromUri(this, uri, 512 * 1024); - if (str == null || !AndroidHostInterface.getInstance().importPatchCodesFromString(str)) { - reportErrorOnUIThread(getString(R.string.emulation_activity_failed_to_import_patch_codes)); - } - } - - private void startDiscChangeFromFile() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_CHANGE_DISC_FILE); - } - - private void showDiscChangeMenu() { - final AndroidHostInterface hi = AndroidHostInterface.getInstance(); - - if (!hi.hasMediaSubImages()) { - startDiscChangeFromFile(); - return; - } - - final String[] paths = AndroidHostInterface.getInstance().getMediaSubImageTitles(); - final int currentPath = AndroidHostInterface.getInstance().getMediaSubImageIndex(); - final int numPaths = (paths != null) ? paths.length : 0; - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - CharSequence[] items = new CharSequence[numPaths + 1]; - for (int i = 0; i < numPaths; i++) - items[i] = FileHelper.getFileNameForPath(paths[i]); - items[numPaths] = getString(R.string.emulation_activity_change_disc_select_new_file); - - builder.setSingleChoiceItems(items, (currentPath < numPaths) ? currentPath : -1, (dialogInterface, i) -> { - dialogInterface.dismiss(); - onMenuClosed(); - - if (i < numPaths) { - AndroidHostInterface.getInstance().switchMediaSubImage(i); - } else { - startDiscChangeFromFile(); - } - }); - builder.setOnCancelListener(dialogInterface -> onMenuClosed()); - builder.create().show(); - } - - private void showAchievementsPopup() { - final Achievement[] achievements = AndroidHostInterface.getInstance().getCheevoList(); - if (achievements == null) { - onMenuClosed(); - return; - } - - final AchievementListFragment alf = new AchievementListFragment(achievements); - alf.show(getSupportFragmentManager(), "fragment_achievement_list"); - alf.setOnDismissListener(dialog -> onMenuClosed()); - } - - /** - * Touchscreen controller overlay - */ - TouchscreenControllerView mTouchscreenController; - - public void updateControllers() { - final int touchscreenControllerIndex = getIntSetting("TouchscreenController/PortIndex", 0); - final String touchscreenControllerPrefix = String.format("Controller%d/", touchscreenControllerIndex + 1); - final String controllerType = getStringSetting(touchscreenControllerPrefix + "Type", "DigitalController"); - final String viewType = getStringSetting("Controller1/TouchscreenControllerView", "digital"); - final boolean autoHideTouchscreenController = getBooleanSetting("Controller1/AutoHideTouchscreenController", false); - final boolean touchGliding = getBooleanSetting("Controller1/TouchGliding", false); - final boolean hapticFeedback = getBooleanSetting("Controller1/HapticFeedback", false); - final boolean vibration = getBooleanSetting("Controller1/Vibration", false); - final FrameLayout activityLayout = findViewById(R.id.frameLayout); - - Log.i("EmulationActivity", "Controller type: " + controllerType); - Log.i("EmulationActivity", "View type: " + viewType); - - mContentView.updateInputDevices(); - AndroidHostInterface.getInstance().updateInputMap(); - - final boolean hasAnyControllers = mContentView.hasAnyGamePads(); - if (controllerType.equals("None") || viewType.equals("none") || (hasAnyControllers && autoHideTouchscreenController)) { - if (mTouchscreenController != null) { - activityLayout.removeView(mTouchscreenController); - mTouchscreenController = null; - } - } else { - if (mTouchscreenController == null) { - mTouchscreenController = new TouchscreenControllerView(this); - activityLayout.addView(mTouchscreenController); - } - - mTouchscreenController.init(touchscreenControllerIndex, controllerType, viewType, hapticFeedback, touchGliding); - } - - if (vibration) - mVibratorService = (Vibrator) getSystemService(VIBRATOR_SERVICE); - else - mVibratorService = null; - - // Place notifications in the middle of the screen, rather then the bottom (because touchscreen). - float notificationVerticalPosition = 1.0f; - float notificationVerticalDirection = -1.0f; - if (mTouchscreenController != null) { - notificationVerticalPosition = 0.3f; - notificationVerticalDirection = -1.0f; - } - AndroidHostInterface.getInstance().setFullscreenUINotificationVerticalPosition( - notificationVerticalPosition, notificationVerticalDirection); - } - - private InputManager.InputDeviceListener mInputDeviceListener; - - private void registerInputDeviceListener() { - if (mInputDeviceListener != null) - return; - - mInputDeviceListener = new InputManager.InputDeviceListener() { - @Override - public void onInputDeviceAdded(int i) { - Log.i("EmulationActivity", String.format("InputDeviceAdded %d", i)); - updateControllers(); - } - - @Override - public void onInputDeviceRemoved(int i) { - Log.i("EmulationActivity", String.format("InputDeviceRemoved %d", i)); - updateControllers(); - } - - @Override - public void onInputDeviceChanged(int i) { - Log.i("EmulationActivity", String.format("InputDeviceChanged %d", i)); - updateControllers(); - } - }; - - InputManager inputManager = ((InputManager) getSystemService(Context.INPUT_SERVICE)); - if (inputManager != null) - inputManager.registerInputDeviceListener(mInputDeviceListener, null); - } - - private void unregisterInputDeviceListener() { - if (mInputDeviceListener == null) - return; - - InputManager inputManager = ((InputManager) getSystemService(Context.INPUT_SERVICE)); - if (inputManager != null) - inputManager.unregisterInputDeviceListener(mInputDeviceListener); - - mInputDeviceListener = null; - } - - private Vibrator mVibratorService; - - public void setVibration(boolean enabled) { - if (mVibratorService == null) - return; - - runOnUiThread(() -> { - if (mVibratorService == null) - return; - - if (enabled) - mVibratorService.vibrate(1000); - else - mVibratorService.cancel(); - }); - } - - private boolean mSustainedPerformanceModeEnabled = false; - - private void updateSustainedPerformanceMode() { - final boolean enabled = getBooleanSetting("Main/SustainedPerformanceMode", false); - if (mSustainedPerformanceModeEnabled == enabled) - return; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - getWindow().setSustainedPerformanceMode(enabled); - Log.i("EmulationActivity", String.format("%s sustained performance mode.", enabled ? "enabling" : "disabling")); - } else { - Log.e("EmulationActivity", "Sustained performance mode not supported."); - } - mSustainedPerformanceModeEnabled = enabled; - - } - - public static class MenuDialogFragment extends DialogFragment { - private EmulationActivity emulationActivity; - private boolean settingsChanged = false; - - public MenuDialogFragment(EmulationActivity emulationActivity) { - this.emulationActivity = emulationActivity; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(STYLE_NO_FRAME, R.style.EmulationActivityOverlay); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_emulation_activity_overlay, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - setContentFragment(new MenuSettingsFragment(this, emulationActivity), false); - - final ImageView coverView =((ImageView)view.findViewById(R.id.cover_image)); - if (emulationActivity.mGameCoverPath != null && !emulationActivity.mGameCoverPath.isEmpty()) { - new ImageLoadTask(coverView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, - emulationActivity.mGameCoverPath); - } else if (emulationActivity.mGameTitle != null) { - new GenerateCoverTask(getContext(), coverView, emulationActivity.mGameTitle) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - coverView.setOnClickListener(v -> close(true)); - - if (emulationActivity.mGameTitle != null) - ((TextView)view.findViewById(R.id.title)).setText(emulationActivity.mGameTitle); - - if (emulationActivity.mGameCode != null && emulationActivity.mGamePath != null) - { - final String subtitle = String.format("%s - %s", emulationActivity.mGameCode, - FileHelper.getFileNameForPath(emulationActivity.mGamePath)); - ((TextView)view.findViewById(R.id.subtitle)).setText(subtitle); - } - - ((ImageButton)view.findViewById(R.id.menu)).setOnClickListener(v -> onMenuClicked()); - ((ImageButton)view.findViewById(R.id.controller_settings)).setOnClickListener(v -> onControllerSettingsClicked()); - ((ImageButton)view.findViewById(R.id.settings)).setOnClickListener(v -> onSettingsClicked()); - ((ImageButton)view.findViewById(R.id.close)).setOnClickListener(v -> close(true)); - } - - @Override - public void onCancel(@NonNull DialogInterface dialog) { - onClosed(true); - } - - private void onClosed(boolean resumeGame) { - if (settingsChanged) - emulationActivity.applySettings(); - - if (resumeGame) - emulationActivity.onMenuClosed(); - - emulationActivity.mPauseMenu = null; - } - - public void close(boolean resumeGame) { - dismiss(); - onClosed(resumeGame); - } - - private void setContentFragment(Fragment fragment, boolean transition) { - FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); - if (transition) - transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out); - transaction.replace(R.id.content, fragment).commit(); - } - - private void onMenuClicked() { - setContentFragment(new MenuSettingsFragment(this, emulationActivity), true); - } - - private void onControllerSettingsClicked() { - ControllerSettingsCollectionFragment fragment = new ControllerSettingsCollectionFragment(); - setContentFragment(fragment, true); - fragment.setMultitapModeChangedListener(this::onControllerSettingsClicked); - settingsChanged = true; - } - - private void onSettingsClicked() { - setContentFragment(new SettingsCollectionFragment(), true); - settingsChanged = true; - } - } - - public static class MenuSettingsFragment extends PreferenceFragmentCompat { - private MenuDialogFragment menuDialogFragment; - private EmulationActivity emulationActivity; - - public MenuSettingsFragment(MenuDialogFragment menuDialogFragment, EmulationActivity emulationActivity) { - this.menuDialogFragment = menuDialogFragment; - this.emulationActivity = emulationActivity; - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext())); - - final boolean cheevosActive = AndroidHostInterface.getInstance().isCheevosActive(); - final boolean cheevosChallengeModeEnabled = AndroidHostInterface.getInstance().isCheevosChallengeModeActive(); - - createPreference(R.string.emulation_menu_load_state, R.drawable.ic_baseline_folder_open_24, !cheevosChallengeModeEnabled, preference -> { - menuDialogFragment.close(false); - emulationActivity.showSaveStateMenu(false); - return true; - }); - createPreference(R.string.emulation_menu_save_state, R.drawable.ic_baseline_save_24, true, preference -> { - menuDialogFragment.close(false); - emulationActivity.showSaveStateMenu(true); - return true; - }); - createPreference(R.string.emulation_menu_toggle_fast_forward, R.drawable.ic_baseline_fast_forward_24, !cheevosChallengeModeEnabled, preference -> { - AndroidHostInterface.getInstance().setFastForwardEnabled(!AndroidHostInterface.getInstance().isFastForwardEnabled()); - menuDialogFragment.close(true); - return true; - }); - createPreference(R.string.emulation_menu_achievements, R.drawable.ic_baseline_trophy_24, cheevosActive, preference -> { - menuDialogFragment.close(false); - emulationActivity.showAchievementsPopup(); - return true; - }); - createPreference(R.string.emulation_menu_exit_game, R.drawable.ic_baseline_exit_to_app_24, true, preference -> { - menuDialogFragment.close(false); - emulationActivity.mStopRequested = true; - emulationActivity.finish(); - return true; - }); - createPreference(R.string.emulation_menu_patch_codes, R.drawable.ic_baseline_tips_and_updates_24, !cheevosChallengeModeEnabled, preference -> { - menuDialogFragment.close(false); - emulationActivity.showPatchesMenu(); - return true; - }); - createPreference(R.string.emulation_menu_change_disc, R.drawable.ic_baseline_album_24, true, preference -> { - menuDialogFragment.close(false); - emulationActivity.showDiscChangeMenu(); - return true; - }); - createPreference(R.string.emulation_menu_touchscreen_controller_settings, R.drawable.ic_baseline_touch_app_24, true, preference -> { - menuDialogFragment.close(false); - emulationActivity.showTouchscreenControllerMenu(); - return true; - }); - createPreference(R.string.emulation_menu_toggle_analog_mode, R.drawable.ic_baseline_gamepad_24, true, preference -> { - AndroidHostInterface.getInstance().toggleControllerAnalogMode(); - menuDialogFragment.close(true); - return true; - }); - createPreference(R.string.emulation_menu_reset_console, R.drawable.ic_baseline_restart_alt_24, true, preference -> { - AndroidHostInterface.getInstance().resetSystem(); - menuDialogFragment.close(true); - return true; - }); - } - - private void createPreference(int titleId, int icon, boolean enabled, Preference.OnPreferenceClickListener action) { - final Preference preference = new Preference(getContext()); - preference.setTitle(titleId); - preference.setIcon(icon); - preference.setOnPreferenceClickListener(action); - preference.setEnabled(enabled); - getPreferenceScreen().addPreference(preference); - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationSurfaceView.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationSurfaceView.java deleted file mode 100644 index 2305caf16..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationSurfaceView.java +++ /dev/null @@ -1,302 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.os.Vibrator; -import android.util.AttributeSet; -import android.util.Log; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.SurfaceView; - -import java.util.ArrayList; -import java.util.List; - -public class EmulationSurfaceView extends SurfaceView { - public EmulationSurfaceView(Context context) { - super(context); - } - - public EmulationSurfaceView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public EmulationSurfaceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public static boolean isBindableDevice(InputDevice inputDevice) { - if (inputDevice == null) - return false; - - // Accept all devices with an axis or buttons, filter in events. - final int sources = inputDevice.getSources(); - return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || - ((sources & InputDevice.SOURCE_CLASS_BUTTON) == InputDevice.SOURCE_CLASS_BUTTON); - } - - public static boolean isGamepadDevice(InputDevice inputDevice) { - final int sources = (inputDevice != null) ? inputDevice.getSources() : 0; - return ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD); - } - - public static boolean isJoystickMotionEvent(MotionEvent event) { - final int source = event.getSource(); - return ((source & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK); - } - - public static boolean isBindableKeyEvent(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_HOME: - case KeyEvent.KEYCODE_POWER: - // We're okay if we get these from a gamepad. - return isGamepadDevice(event.getDevice()); - - default: - return true; - } - } - - private static boolean isSystemKeyCode(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_MENU: - case KeyEvent.KEYCODE_SOFT_RIGHT: - case KeyEvent.KEYCODE_HOME: - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_CALL: - case KeyEvent.KEYCODE_ENDCALL: - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - case KeyEvent.KEYCODE_VOLUME_MUTE: - case KeyEvent.KEYCODE_MUTE: - case KeyEvent.KEYCODE_POWER: - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY: - case KeyEvent.KEYCODE_MEDIA_PAUSE: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MEDIA_STOP: - case KeyEvent.KEYCODE_MEDIA_NEXT: - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - case KeyEvent.KEYCODE_MEDIA_RECORD: - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - case KeyEvent.KEYCODE_CAMERA: - case KeyEvent.KEYCODE_FOCUS: - case KeyEvent.KEYCODE_SEARCH: - case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: - case KeyEvent.KEYCODE_BRIGHTNESS_UP: - case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: - case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP: - case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN: - case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT: - case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: - return true; - - default: - return false; - } - } - - private class InputDeviceData { - private int deviceId; - private String descriptor; - private int[] axes; - private float[] axisValues; - private int controllerIndex; - private Vibrator vibrator; - - public InputDeviceData(InputDevice device, int controllerIndex) { - deviceId = device.getId(); - descriptor = device.getDescriptor(); - this.controllerIndex = controllerIndex; - - List motionRanges = device.getMotionRanges(); - if (motionRanges != null && !motionRanges.isEmpty()) { - axes = new int[motionRanges.size()]; - axisValues = new float[motionRanges.size()]; - for (int i = 0; i < motionRanges.size(); i++) - axes[i] = motionRanges.get(i).getAxis(); - } - - // device.getVibrator() always returns null, but might return a "null vibrator". - final Vibrator potentialVibrator = device.getVibrator(); - if (potentialVibrator != null && potentialVibrator.hasVibrator()) - vibrator = potentialVibrator; - } - } - - private InputDeviceData[] mInputDevices = null; - private String[] mControllerDescriptors = null; - private boolean mHasAnyGamepads = false; - - public boolean hasAnyGamePads() { - return mHasAnyGamepads; - } - - public synchronized void updateInputDevices() { - mInputDevices = null; - mControllerDescriptors = null; - mHasAnyGamepads = false; - - final ArrayList inputDeviceIds = new ArrayList<>(); - final ArrayList controllerDescriptors = new ArrayList<>(); - - for (int deviceId : InputDevice.getDeviceIds()) { - final InputDevice device = InputDevice.getDevice(deviceId); - if (device == null || !isBindableDevice(device)) { - Log.d("EmulationSurfaceView", - String.format("Skipping device %s sources %d", - (device != null) ? device.toString() : "", - (device != null) ? device.getSources() : 0)); - continue; - } - - if (isGamepadDevice(device)) - mHasAnyGamepads = true; - - // Some phones seem to have duplicate descriptors for multiple devices. - // Combine them all into one controller index if so. - final String descriptor = device.getDescriptor(); - int controllerIndex = controllerDescriptors.size(); - for (int i = 0; i < controllerDescriptors.size(); i++) { - if (controllerDescriptors.get(i).equals(descriptor)) { - controllerIndex = i; - break; - } - } - if (controllerIndex == controllerDescriptors.size()) { - controllerDescriptors.add(descriptor); - } - - Log.d("EmulationSurfaceView", String.format("Tracking device %d/%s (%s, sources %d, controller %d)", - controllerIndex, descriptor, device.getName(), device.getSources(), controllerIndex)); - inputDeviceIds.add(new InputDeviceData(device, controllerIndex)); - } - - if (inputDeviceIds.isEmpty()) - return; - - mInputDevices = new InputDeviceData[inputDeviceIds.size()]; - inputDeviceIds.toArray(mInputDevices); - - mControllerDescriptors = new String[controllerDescriptors.size()]; - controllerDescriptors.toArray(mControllerDescriptors); - } - - public synchronized String[] getInputDeviceNames() { - return mControllerDescriptors; - } - - public synchronized boolean hasInputDeviceVibration(int controllerIndex) { - if (mInputDevices == null || controllerIndex >= mInputDevices.length) - return false; - - return (mInputDevices[controllerIndex].vibrator != null); - } - - public synchronized void setInputDeviceVibration(int controllerIndex, float smallMotor, float largeMotor) { - if (mInputDevices == null || controllerIndex >= mInputDevices.length) - return; - - // shouldn't get here - final InputDeviceData data = mInputDevices[controllerIndex]; - if (data.vibrator == null) - return; - - final float MINIMUM_INTENSITY = 0.1f; - if (smallMotor >= MINIMUM_INTENSITY || largeMotor >= MINIMUM_INTENSITY) - data.vibrator.vibrate(1000); - else - data.vibrator.cancel(); - } - - public InputDeviceData getDataForDeviceId(int deviceId) { - if (mInputDevices == null) - return null; - - for (InputDeviceData data : mInputDevices) { - if (data.deviceId == deviceId) - return data; - } - - return null; - } - - public int getControllerIndexForDeviceId(int deviceId) { - final InputDeviceData data = getDataForDeviceId(deviceId); - return (data != null) ? data.controllerIndex : -1; - } - - private boolean handleKeyEvent(int deviceId, int repeatCount, int keyCode, boolean pressed) { - final int controllerIndex = getControllerIndexForDeviceId(deviceId); - Log.d("EmulationSurfaceView", String.format("Controller %d Code %d RC %d Pressed %d", - controllerIndex, keyCode, repeatCount, pressed? 1 : 0)); - - final AndroidHostInterface hi = AndroidHostInterface.getInstance(); - if (repeatCount == 0 && controllerIndex >= 0) - hi.handleControllerButtonEvent(controllerIndex, keyCode, pressed); - - // We don't want to eat external button events unless it's actually bound. - if (isSystemKeyCode(keyCode)) - return (controllerIndex >= 0 && hi.hasControllerButtonBinding(controllerIndex, keyCode)); - else - return true; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return handleKeyEvent(event.getDeviceId(), event.getRepeatCount(), keyCode, true); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return handleKeyEvent(event.getDeviceId(), 0, keyCode, false); - } - - private float clamp(float value, float min, float max) { - return (value < min) ? min : ((value > max) ? max : value); - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (!isJoystickMotionEvent(event)) - return false; - - final InputDeviceData data = getDataForDeviceId(event.getDeviceId()); - if (data == null || data.axes == null) - return false; - - for (int i = 0; i < data.axes.length; i++) { - final int axis = data.axes[i]; - final float axisValue = event.getAxisValue(axis); - float emuValue; - - switch (axis) { - case MotionEvent.AXIS_BRAKE: - case MotionEvent.AXIS_GAS: - case MotionEvent.AXIS_LTRIGGER: - case MotionEvent.AXIS_RTRIGGER: - // Scale 0..1 -> -1..1. - emuValue = (clamp(axisValue, 0.0f, 1.0f) * 2.0f) - 1.0f; - break; - - default: - // Everything else should already by -1..1 as per Android documentation. - emuValue = clamp(axisValue, -1.0f, 1.0f); - break; - } - - if (data.axisValues[i] == emuValue) - continue; - - Log.d("EmulationSurfaceView", - String.format("axis %d value %f emuvalue %f", axis, axisValue, emuValue)); - - data.axisValues[i] = emuValue; - AndroidHostInterface.getInstance().handleControllerAxisEvent(data.controllerIndex, axis, emuValue); - } - - return true; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java deleted file mode 100644 index 0bdcf25d9..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.Build; -import android.os.Process; -import android.util.Log; -import android.view.Surface; - -import androidx.annotation.NonNull; - -public class EmulationThread extends Thread { - private EmulationActivity emulationActivity; - private String filename; - private boolean resumeState; - private String stateFilename; - - private EmulationThread(EmulationActivity emulationActivity, String filename, boolean resumeState, String stateFilename) { - super("EmulationThread"); - this.emulationActivity = emulationActivity; - this.filename = filename; - this.resumeState = resumeState; - this.stateFilename = stateFilename; - } - - public static EmulationThread create(EmulationActivity emulationActivity, String filename, boolean resumeState, String stateFilename) { - Log.i("EmulationThread", String.format("Starting emulation thread (%s)...", filename)); - - EmulationThread thread = new EmulationThread(emulationActivity, filename, resumeState, stateFilename); - thread.start(); - return thread; - } - - private void setExclusiveCores() { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - int[] cores = Process.getExclusiveCores(); - if (cores == null || cores.length == 0) - throw new Exception("Invalid return value from getExclusiveCores()"); - - AndroidHostInterface.setThreadAffinity(cores); - } - } catch (Exception e) { - Log.e("EmulationThread", "getExclusiveCores() failed"); - } - } - - @Override - public void run() { - try { - Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE); - setExclusiveCores(); - } catch (Exception e) { - Log.i("EmulationThread", "Failed to set priority for emulation thread: " + e.getMessage()); - } - - AndroidHostInterface.getInstance().runEmulationThread(emulationActivity, filename, resumeState, stateFilename); - Log.i("EmulationThread", "Emulation thread exiting."); - } - - public void stopAndJoin() { - AndroidHostInterface.getInstance().stopEmulationThreadLoop(); - try { - join(); - } catch (InterruptedException e) { - Log.i("EmulationThread", "join() interrupted: " + e.getMessage()); - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java deleted file mode 100644 index 258350d0d..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileHelper.java +++ /dev/null @@ -1,613 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.ImageDecoder; -import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.os.storage.StorageManager; -import android.provider.DocumentsContract; -import android.provider.MediaStore; - -import androidx.annotation.Nullable; - -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.OutputStream; -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -/** - * File helper class - used to bridge native code to Java storage access framework APIs. - */ -public class FileHelper { - /** - * Native filesystem flags. - */ - public static final int FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY = 1; - public static final int FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY = 2; - public static final int FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED = 4; - - /** - * Native filesystem find result flags. - */ - public static final int FILESYSTEM_FIND_RECURSIVE = (1 << 0); - public static final int FILESYSTEM_FIND_RELATIVE_PATHS = (1 << 1); - public static final int FILESYSTEM_FIND_HIDDEN_FILES = (1 << 2); - public static final int FILESYSTEM_FIND_FOLDERS = (1 << 3); - public static final int FILESYSTEM_FIND_FILES = (1 << 4); - public static final int FILESYSTEM_FIND_KEEP_ARRAY = (1 << 5); - - /** - * Projection used when searching for files. - */ - private static final String[] findProjection = new String[]{ - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE, - DocumentsContract.Document.COLUMN_SIZE, - DocumentsContract.Document.COLUMN_LAST_MODIFIED - }; - - /** - * Projection used when getting the display name for a file. - */ - private static final String[] getDisplayNameProjection = new String[]{ - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - }; - - /** - * Projection used when getting a relative file for a file. - */ - private static final String[] getRelativeFileProjection = new String[]{ - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - }; - - private final Context context; - private final ContentResolver contentResolver; - - /** - * File helper class - used to bridge native code to Java storage access framework APIs. - * - * @param context Context in which to perform file actions as. - */ - public FileHelper(Context context) { - this.context = context; - this.contentResolver = context.getContentResolver(); - } - - /** - * Reads the specified file as a string, under the specified context. - * - * @param context context to access file under - * @param uri uri to write data to - * @param maxSize maximum file size to read - * @return String containing the file data, otherwise null - */ - public static String readStringFromUri(final Context context, final Uri uri, int maxSize) { - InputStream stream = null; - try { - stream = context.getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - return null; - } - - StringBuilder os = new StringBuilder(); - try { - char[] buffer = new char[1024]; - InputStreamReader reader = new InputStreamReader(stream, Charset.forName(StandardCharsets.UTF_8.name())); - int len; - while ((len = reader.read(buffer)) > 0) { - os.append(buffer, 0, len); - if (os.length() > maxSize) - return null; - } - - stream.close(); - } catch (IOException e) { - return null; - } - - if (os.length() == 0) - return null; - - return os.toString(); - } - - /** - * Reads the specified file as a byte array, under the specified context. - * - * @param context context to access file under - * @param uri uri to write data to - * @param maxSize maximum file size to read - * @return byte array containing the file data, otherwise null - */ - public static byte[] readBytesFromUri(final Context context, final Uri uri, int maxSize) { - InputStream stream = null; - try { - stream = context.getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - return null; - } - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - byte[] buffer = new byte[512 * 1024]; - int len; - while ((len = stream.read(buffer)) > 0) { - os.write(buffer, 0, len); - if (maxSize > 0 && os.size() > maxSize) { - return null; - } - } - - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - - if (os.size() == 0) - return null; - - return os.toByteArray(); - } - - /** - * Writes the specified data to a file referenced by the URI, as the specified context. - * - * @param context context to access file under - * @param uri uri to write data to - * @param bytes data to write file to - * @return true if write was succesful, otherwise false - */ - public static boolean writeBytesToUri(final Context context, final Uri uri, final byte[] bytes) { - OutputStream stream = null; - try { - stream = context.getContentResolver().openOutputStream(uri); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return false; - } - - if (bytes != null && bytes.length > 0) { - try { - stream.write(bytes); - stream.close(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - return true; - } - - /** - * Deletes the file referenced by the URI, under the specified context. - * - * @param context context to delete file under - * @param uri uri to delete - * @return - */ - public static boolean deleteFileAtUri(final Context context, final Uri uri) { - try { - if (uri.getScheme() == "file") { - final File file = new File(uri.getPath()); - if (!file.isFile()) - return false; - - return file.delete(); - } - return (context.getContentResolver().delete(uri, null, null) > 0); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - /** - * Returns the name of the file pointed at by a SAF URI. - * - * @param context context to access file under - * @param uri uri to retrieve file name for - * @return the name of the file, or null - */ - public static String getDocumentNameFromUri(final Context context, final Uri uri) { - Cursor cursor = null; - try { - final String[] proj = {DocumentsContract.Document.COLUMN_DISPLAY_NAME}; - cursor = context.getContentResolver().query(uri, proj, null, null, null); - final int columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME); - cursor.moveToFirst(); - return cursor.getString(columnIndex); - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (cursor != null) - cursor.close(); - } - } - - /** - * Loads a bitmap from the provided SAF URI. - * - * @param context context to access file under - * @param uri uri to retrieve file name for - * @return a decoded bitmap for the file, or null - */ - public static Bitmap loadBitmapFromUri(final Context context, final Uri uri) { - InputStream stream = null; - try { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { - final ImageDecoder.Source source = ImageDecoder.createSource(context.getContentResolver(), uri); - return ImageDecoder.decodeBitmap(source); - } else { - return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Returns the file name component of a path or URI. - * - * @param path Path/URI to examine. - * @return File name component of path/URI. - */ - public static String getFileNameForPath(String path) { - if (path.startsWith("content:/") || path.startsWith("file:/")) { - try { - final Uri uri = Uri.parse(path); - final String lastPathSegment = uri.getLastPathSegment(); - if (lastPathSegment != null) - path = lastPathSegment; - } catch (Exception e) { - } - } - - int lastSlash = path.lastIndexOf('/'); - if (lastSlash > 0 && lastSlash < path.length() - 1) - return path.substring(lastSlash + 1); - else - return path; - } - - /** - * Test if the given URI represents a {@link DocumentsContract.Document} tree. - */ - public static boolean isTreeUri(Uri uri) { - final List paths = uri.getPathSegments(); - return (paths.size() >= 2 && paths.get(0).equals("tree")); - } - - /** - * Retrieves a file descriptor for a content URI string. Called by native code. - * - * @param uriString string of the URI to open - * @param mode Java open mode - * @return file descriptor for URI, or -1 - */ - public int openURIAsFileDescriptor(String uriString, String mode) { - try { - final Uri uri = Uri.parse(uriString); - final ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, mode); - if (fd == null) - return -1; - return fd.detachFd(); - } catch (Exception e) { - return -1; - } - } - - /** - * Recursively iterates documents in the specified tree, searching for files. - * - * @param treeUri Root tree in which to search for documents. - * @param documentId Document ID representing the directory to start searching. - * @param flags Native search flags. - * @param results Cumulative result array. - */ - private void doFindFiles(Uri treeUri, String documentId, int flags, ArrayList results) { - try { - final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId); - final Cursor cursor = contentResolver.query(queryUri, findProjection, null, null, null); - final int count = cursor.getCount(); - - while (cursor.moveToNext()) { - try { - final String mimeType = cursor.getString(2); - final String childDocumentId = cursor.getString(0); - final Uri uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocumentId); - final long size = cursor.getLong(3); - final long lastModified = cursor.getLong(4); - - if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) { - if ((flags & FILESYSTEM_FIND_FOLDERS) != 0) { - results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)); - } - - if ((flags & FILESYSTEM_FIND_RECURSIVE) != 0) - doFindFiles(treeUri, childDocumentId, flags, results); - } else { - if ((flags & FILESYSTEM_FIND_FILES) != 0) { - results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, 0)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - cursor.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Recursively iterates documents in the specified URI, searching for files. - * - * @param uriString URI containing directory to search. - * @param flags Native filter flags. - * @return Array of find results. - */ - public FindResult[] findFiles(String uriString, int flags) { - try { - final Uri fullUri = Uri.parse(uriString); - final String documentId = DocumentsContract.getTreeDocumentId(fullUri); - final ArrayList results = new ArrayList<>(); - doFindFiles(fullUri, documentId, flags, results); - if (results.isEmpty()) - return null; - - final FindResult[] resultsArray = new FindResult[results.size()]; - results.toArray(resultsArray); - return resultsArray; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Returns the display name for the given URI. - * - * @param uriString URI to resolve display name for. - * @return display name for the URI, or null. - */ - public String getDisplayNameForURIPath(String uriString) { - try { - final Uri fullUri = Uri.parse(uriString); - final Cursor cursor = contentResolver.query(fullUri, getDisplayNameProjection, - null, null, null); - if (cursor.getCount() == 0 || !cursor.moveToNext()) - return null; - - return cursor.getString(0); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - /** - * Returns the path for a sibling file relative to another URI. - * - * @param uriString URI to find the file relative to. - * @param newFileName Sibling file name. - * @return URI for the sibling file name, or null. - */ - public String getRelativePathForURIPath(String uriString, String newFileName) { - try { - final Uri fullUri = Uri.parse(uriString); - - // if this is a document (expected)... - Uri treeUri; - String treeDocId; - if (DocumentsContract.isDocumentUri(context, fullUri)) { - // we need to remove the last part of the URI (the specific document ID) to get the parent - final String lastPathSegment = fullUri.getLastPathSegment(); - int lastSeparatorIndex = lastPathSegment.lastIndexOf('/'); - if (lastSeparatorIndex < 0) - lastSeparatorIndex = lastPathSegment.lastIndexOf(':'); - if (lastSeparatorIndex < 0) - return null; - - // the parent becomes the document ID - treeDocId = lastPathSegment.substring(0, lastSeparatorIndex); - - // but, we need to access it through the subtree if this was a tree URI (permissions...) - if (isTreeUri(fullUri)) { - treeUri = DocumentsContract.buildTreeDocumentUri(fullUri.getAuthority(), DocumentsContract.getTreeDocumentId(fullUri)); - } else { - treeUri = DocumentsContract.buildTreeDocumentUri(fullUri.getAuthority(), treeDocId); - } - } else { - treeDocId = DocumentsContract.getDocumentId(fullUri); - treeUri = fullUri; - } - - final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, treeDocId); - final Cursor cursor = contentResolver.query(queryUri, getRelativeFileProjection, null, null, null); - final int count = cursor.getCount(); - - while (cursor.moveToNext()) { - try { - final String displayName = cursor.getString(1); - if (!displayName.equalsIgnoreCase(newFileName)) - continue; - - final String childDocumentId = cursor.getString(0); - final Uri uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocumentId); - cursor.close(); - return uri.toString(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - cursor.close(); - return null; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - private static final String PRIMARY_VOLUME_NAME = "primary"; - - @Nullable - public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) { - if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con); - if (volumePath == null) return File.separator; - if (volumePath.endsWith(File.separator)) - volumePath = volumePath.substring(0, volumePath.length() - 1); - - String documentPath = getDocumentPathFromTreeUri(treeUri); - if (documentPath.endsWith(File.separator)) - documentPath = documentPath.substring(0, documentPath.length() - 1); - - if (documentPath.length() > 0) { - if (documentPath.startsWith(File.separator)) - return volumePath + documentPath; - else - return volumePath + File.separator + documentPath; - } else return volumePath; - } - - @SuppressLint("ObsoleteSdkInt") - private static String getVolumePath(final String volumeId, Context context) { - if (volumeId == null) - return null; - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null; - try { - StorageManager mStorageManager = - (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - Class storageVolumeClazz = Class.forName("android.os.storage.StorageVolume"); - Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList"); - Method getUuid = storageVolumeClazz.getMethod("getUuid"); - Method getPath = storageVolumeClazz.getMethod("getPath"); - Method isPrimary = storageVolumeClazz.getMethod("isPrimary"); - Object result = getVolumeList.invoke(mStorageManager); - - final int length = Array.getLength(result); - for (int i = 0; i < length; i++) { - Object storageVolumeElement = Array.get(result, i); - String uuid = (String) getUuid.invoke(storageVolumeElement); - Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement); - - // primary volume? - if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) - return (String) getPath.invoke(storageVolumeElement); - - // other volumes? - if (uuid != null && uuid.equals(volumeId)) - return (String) getPath.invoke(storageVolumeElement); - } - // not found. - return null; - } catch (Exception ex) { - return null; - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getVolumeIdFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getTreeDocumentId(treeUri); - final String[] split = docId.split(":"); - if (split.length > 0) return split[0]; - else return null; - } - - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getDocumentPathFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getTreeDocumentId(treeUri); - final String[] split = docId.split(":"); - if ((split.length >= 2) && (split[1] != null)) return split[1]; - else return File.separator; - } - - @Nullable - public static String getFullPathFromUri(@Nullable final Uri treeUri, Context con) { - if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromUri(treeUri), con); - if (volumePath == null) return File.separator; - if (volumePath.endsWith(File.separator)) - volumePath = volumePath.substring(0, volumePath.length() - 1); - - String documentPath = getDocumentPathFromUri(treeUri); - if (documentPath.endsWith(File.separator)) - documentPath = documentPath.substring(0, documentPath.length() - 1); - - if (documentPath.length() > 0) { - if (documentPath.startsWith(File.separator)) - return volumePath + documentPath; - else - return volumePath + File.separator + documentPath; - } else return volumePath; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getVolumeIdFromUri(final Uri treeUri) { - try { - final String docId = DocumentsContract.getDocumentId(treeUri); - final String[] split = docId.split(":"); - if (split.length > 0) return split[0]; - else return null; - } catch (Exception e) { - return null; - } - } - - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static String getDocumentPathFromUri(final Uri treeUri) { - try { - final String docId = DocumentsContract.getDocumentId(treeUri); - final String[] split = docId.split(":"); - if ((split.length >= 2) && (split[1] != null)) return split[1]; - else return File.separator; - } catch (Exception e) { - return null; - } - } - - /** - * Java class containing the data for a file in a find operation. - */ - public static class FindResult { - public String name; - public String relativeName; - public long size; - public long modifiedTime; - public int flags; - - public FindResult(String relativeName, String name, long size, long modifiedTime, int flags) { - this.relativeName = relativeName; - this.name = name; - this.size = size; - this.modifiedTime = modifiedTime; - this.flags = flags; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java deleted file mode 100644 index 4c1bd85b2..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameDirectoriesActivity.java +++ /dev/null @@ -1,348 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.os.FileUtils; -import android.util.Log; -import android.util.Property; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ListAdapter; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.ListFragment; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; - -import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - -public class GameDirectoriesActivity extends AppCompatActivity { - private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 1; - private static final String FORCE_SAF_CONFIG_KEY = "GameList/ForceStorageAccessFramework"; - - private class DirectoryListAdapter extends RecyclerView.Adapter { - private class Entry { - private String mPath; - private boolean mRecursive; - - public Entry(String path, boolean recursive) { - mPath = path; - mRecursive = recursive; - } - - public String getPath() { - return mPath; - } - - public boolean isRecursive() { - return mRecursive; - } - - public void toggleRecursive() { - mRecursive = !mRecursive; - } - } - - private class EntryComparator implements Comparator { - @Override - public int compare(Entry left, Entry right) { - return left.getPath().compareTo(right.getPath()); - } - } - - private Context mContext; - private Entry[] mEntries; - - public DirectoryListAdapter(Context context) { - mContext = context; - reload(); - } - - public void reload() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); - ArrayList entries = new ArrayList<>(); - - try { - Set paths = prefs.getStringSet("GameList/Paths", null); - if (paths != null) { - for (String path : paths) - entries.add(new Entry(path, false)); - } - } catch (Exception e) { - } - - try { - Set paths = prefs.getStringSet("GameList/RecursivePaths", null); - if (paths != null) { - for (String path : paths) - entries.add(new Entry(path, true)); - } - } catch (Exception e) { - } - - mEntries = new Entry[entries.size()]; - entries.toArray(mEntries); - Arrays.sort(mEntries, new EntryComparator()); - notifyDataSetChanged(); - } - - private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private int mPosition; - private Entry mEntry; - private TextView mPathView; - private TextView mRecursiveView; - private ImageButton mToggleRecursiveView; - private ImageButton mRemoveView; - - public ViewHolder(View rootView) { - super(rootView); - mPathView = rootView.findViewById(R.id.path); - mRecursiveView = rootView.findViewById(R.id.recursive); - mToggleRecursiveView = rootView.findViewById(R.id.toggle_recursive); - mToggleRecursiveView.setOnClickListener(this); - mRemoveView = rootView.findViewById(R.id.remove); - mRemoveView.setOnClickListener(this); - } - - public void bindData(int position, Entry entry) { - mPosition = position; - mEntry = entry; - updateText(); - } - - private void updateText() { - mPathView.setText(mEntry.getPath()); - mRecursiveView.setText(getString(mEntry.isRecursive() ? R.string.game_directories_scanning_subdirectories : R.string.game_directories_not_scanning_subdirectories)); - mToggleRecursiveView.setImageDrawable(getDrawable(mEntry.isRecursive() ? R.drawable.ic_baseline_folder_24 : R.drawable.ic_baseline_folder_open_24)); - } - - @Override - public void onClick(View v) { - if (mToggleRecursiveView == v) { - removeSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive()); - mEntry.toggleRecursive(); - addSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive()); - updateText(); - } else if (mRemoveView == v) { - removeSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive()); - reload(); - } - } - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - ((ViewHolder) holder).bindData(position, mEntries[position]); - } - - @Override - public int getItemViewType(int position) { - return R.layout.layout_game_directory_entry; - } - - @Override - public long getItemId(int position) { - return mEntries[position].getPath().hashCode(); - } - - @Override - public int getItemCount() { - return mEntries.length; - } - } - - DirectoryListAdapter mDirectoryListAdapter; - RecyclerView mRecyclerView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_game_directories); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - mDirectoryListAdapter = new DirectoryListAdapter(this); - mRecyclerView = findViewById(R.id.recycler_view); - mRecyclerView.setAdapter(mDirectoryListAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(), - DividerItemDecoration.VERTICAL)); - - findViewById(R.id.fab).setOnClickListener((v) -> startAddGameDirectory()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_edit_game_directories, menu); - - menu.findItem(R.id.force_saf) - .setEnabled(android.os.Build.VERSION.SDK_INT < 30) - .setChecked(PreferenceManager.getDefaultSharedPreferences(this).getBoolean( - FORCE_SAF_CONFIG_KEY, false)) - .setOnMenuItemClickListener(item -> { - final SharedPreferences sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(this); - final boolean newValue =!sharedPreferences.getBoolean( - FORCE_SAF_CONFIG_KEY, false); - sharedPreferences.edit() - .putBoolean(FORCE_SAF_CONFIG_KEY, newValue) - .commit(); - item.setChecked(newValue); - return true; - }); - - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - if (item.getItemId() == R.id.add_directory) { - startAddGameDirectory(); - return true; - } else if (item.getItemId() == R.id.add_path) { - startAddPath(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - public static boolean useStorageAccessFramework(Context context) { - // Use legacy storage on devices older than Android 11... apparently some of them - // have broken storage access framework.... - if (android.os.Build.VERSION.SDK_INT >= 30) - return true; - - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean( - "GameList/ForceStorageAccessFramework", false); - } - - public static void addSearchDirectory(Context context, String path, boolean recursive) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final String key = recursive ? "GameList/RecursivePaths" : "GameList/Paths"; - PreferenceHelpers.addToStringList(prefs, key, path); - Log.i("GameDirectoriesActivity", "Added path '" + path + "' to game list search directories"); - } - - public static void removeSearchDirectory(Context context, String path, boolean recursive) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - final String key = recursive ? "GameList/RecursivePaths" : "GameList/Paths"; - PreferenceHelpers.removeFromStringList(prefs, key, path); - Log.i("GameDirectoriesActivity", "Removed path '" + path + "' from game list search directories"); - } - - private void startAddGameDirectory() { - Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - i.addCategory(Intent.CATEGORY_DEFAULT); - i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - startActivityForResult(Intent.createChooser(i, getString(R.string.main_activity_choose_directory)), - REQUEST_ADD_DIRECTORY_TO_GAME_LIST); - } - - private void startAddPath() { - final EditText text = new EditText(this); - text.setSingleLine(); - - new AlertDialog.Builder(this) - .setTitle(R.string.edit_game_directories_add_path) - .setMessage(R.string.edit_game_directories_add_path_summary) - .setView(text) - .setPositiveButton("Add", (dialog, which) -> { - final String path = text.getText().toString(); - if (!path.isEmpty()) { - addSearchDirectory(GameDirectoriesActivity.this, path, true); - mDirectoryListAdapter.reload(); - } - }) - .setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()) - .show(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - switch (requestCode) { - case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: { - if (resultCode != RESULT_OK || data.getData() == null) - return; - - // Use legacy storage on devices older than Android 11... apparently some of them - // have broken storage access framework.... - if (!useStorageAccessFramework(this)) { - final String path = FileHelper.getFullPathFromTreeUri(data.getData(), this); - if (path != null) { - addSearchDirectory(this, path, true); - mDirectoryListAdapter.reload(); - return; - } - } - - try { - getContentResolver().takePersistableUriPermission(data.getData(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } catch (Exception e) { - Toast.makeText(this, "Failed to take persistable permission.", Toast.LENGTH_LONG); - e.printStackTrace(); - } - - addSearchDirectory(this, data.getDataString(), true); - mDirectoryListAdapter.reload(); - } - break; - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameGridFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameGridFragment.java deleted file mode 100644 index d72b80501..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameGridFragment.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; - -public class GameGridFragment extends Fragment implements GameList.OnRefreshListener { - private static final int SPACING_DIPS = 25; - private static final int WIDTH_DIPS = 160; - - private final MainActivity mParent; - private RecyclerView mRecyclerView; - private ViewAdapter mAdapter; - private GridAutofitLayoutManager mLayoutManager; - - public GameGridFragment(MainActivity parent) { - super(R.layout.fragment_game_grid); - this.mParent = parent; - } - - private GameList getGameList() { - return mParent.getGameList(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mAdapter = new ViewAdapter(mParent, getGameList()); - getGameList().addRefreshListener(this); - - mRecyclerView = view.findViewById(R.id.game_list_view); - mRecyclerView.setAdapter(mAdapter); - - final int columnWidth = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - WIDTH_DIPS + SPACING_DIPS, getResources().getDisplayMetrics())); - mLayoutManager = new GridAutofitLayoutManager(getContext(), columnWidth); - mRecyclerView.setLayoutManager(mLayoutManager); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - getGameList().removeRefreshListener(this); - } - - @Override - public void onGameListRefresh() { - mAdapter.notifyDataSetChanged(); - } - - private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - private final MainActivity mParent; - private final ImageView mImageView; - private GameListEntry mEntry; - - public ViewHolder(@NonNull MainActivity parent, @NonNull View itemView) { - super(itemView); - mParent = parent; - mImageView = itemView.findViewById(R.id.imageView); - mImageView.setOnClickListener(this); - mImageView.setOnLongClickListener(this); - } - - public void bindToEntry(GameListEntry entry) { - mEntry = entry; - - // while it loads/generates - mImageView.setImageDrawable(ContextCompat.getDrawable(mParent, R.drawable.ic_media_cdrom)); - - final String coverPath = entry.getCoverPath(); - if (coverPath == null) { - new GenerateCoverTask(mParent, mImageView, mEntry.getTitle()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - return; - } - - new ImageLoadTask(mImageView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, coverPath); - } - - @Override - public void onClick(View v) { - mParent.startEmulation(mEntry.getPath(), mParent.shouldResumeStateByDefault()); - } - - @Override - public boolean onLongClick(View v) { - mParent.openGamePopupMenu(v, mEntry); - return true; - } - } - - private static class ViewAdapter extends RecyclerView.Adapter { - private final MainActivity mParent; - private final LayoutInflater mInflater; - private final GameList mGameList; - - public ViewAdapter(@NonNull MainActivity parent, @NonNull GameList gameList) { - mParent = parent; - mInflater = LayoutInflater.from(parent); - mGameList = gameList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(mParent, mInflater.inflate(R.layout.layout_game_grid_entry, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - GameListEntry entry = mGameList.getEntry(position); - holder.bindToEntry(entry); - } - - @Override - public int getItemCount() { - return mGameList.getEntryCount(); - } - - @Override - public int getItemViewType(int position) { - return R.layout.layout_game_grid_entry; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java deleted file mode 100644 index f835dce22..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameList.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.Activity; -import android.os.AsyncTask; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; - -public class GameList { - public interface OnRefreshListener { - void onGameListRefresh(); - } - - private Activity mContext; - private GameListEntry[] mEntries; - private ArrayList mRefreshListeners = new ArrayList<>(); - - public GameList(Activity context) { - mContext = context; - mEntries = new GameListEntry[0]; - } - - public void addRefreshListener(OnRefreshListener listener) { - mRefreshListeners.add(listener); - } - public void removeRefreshListener(OnRefreshListener listener) { - mRefreshListeners.remove(listener); - } - public void fireRefreshListeners() { - for (OnRefreshListener listener : mRefreshListeners) - listener.onGameListRefresh(); - } - - private class GameListEntryComparator implements Comparator { - @Override - public int compare(GameListEntry left, GameListEntry right) { - return left.getTitle().compareTo(right.getTitle()); - } - } - - public void refresh(boolean invalidateCache, boolean invalidateDatabase, Activity parentActivity) { - // Search and get entries from native code - AndroidProgressCallback progressCallback = new AndroidProgressCallback(mContext); - AsyncTask.execute(() -> { - AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase, progressCallback); - GameListEntry[] newEntries = AndroidHostInterface.getInstance().getGameListEntries(); - Arrays.sort(newEntries, new GameListEntryComparator()); - - mContext.runOnUiThread(() -> { - try { - progressCallback.dismiss(); - } catch (Exception e) { - Log.e("GameList", "Exception dismissing refresh progress"); - e.printStackTrace(); - } - mEntries = newEntries; - fireRefreshListeners(); - }); - }); - } - - public int getEntryCount() { - return mEntries.length; - } - - public GameListEntry getEntry(int index) { - return mEntries[index]; - } - - public GameListEntry getEntryForPath(String path) { - for (final GameListEntry entry : mEntries) { - if (entry.getPath().equals(path)) - return entry; - } - - return null; - } -} 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 deleted file mode 100644 index 95e5674b5..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.AsyncTask; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.core.content.ContextCompat; - -public class GameListEntry { - public enum EntryType { - Disc, - PSExe, - Playlist, - PSF - } - - public enum CompatibilityRating { - Unknown, - DoesntBoot, - CrashesInIntro, - CrashesInGame, - GraphicalAudioIssues, - NoIssues, - } - - private String mPath; - private String mCode; - private String mTitle; - private long mSize; - private String mModifiedTime; - private DiscRegion mRegion; - private EntryType mType; - private CompatibilityRating mCompatibilityRating; - private String mCoverPath; - - public GameListEntry(String path, String code, String title, long size, String modifiedTime, String region, - String type, String compatibilityRating, String coverPath) { - mPath = path; - mCode = code; - mTitle = title; - mSize = size; - mModifiedTime = modifiedTime; - mCoverPath = coverPath; - - try { - mRegion = DiscRegion.valueOf(region); - } catch (IllegalArgumentException e) { - mRegion = DiscRegion.NTSC_U; - } - - try { - mType = EntryType.valueOf(type); - } catch (IllegalArgumentException e) { - mType = EntryType.Disc; - } - - try { - mCompatibilityRating = CompatibilityRating.valueOf(compatibilityRating); - } catch (IllegalArgumentException e) { - mCompatibilityRating = CompatibilityRating.Unknown; - } - } - - public String getPath() { - return mPath; - } - - public String getCode() { - return mCode; - } - - public String getTitle() { - return mTitle; - } - - public long getSize() { return mSize; } - - public String getModifiedTime() { - return mModifiedTime; - } - - public DiscRegion getRegion() { - return mRegion; - } - - public EntryType getType() { - return mType; - } - - public CompatibilityRating getCompatibilityRating() { - return mCompatibilityRating; - } - - public String getCoverPath() { return mCoverPath; } - - public void setCoverPath(String coverPath) { mCoverPath = coverPath; } - -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameListFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameListFragment.java deleted file mode 100644 index 01635d7b5..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameListFragment.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -public class GameListFragment extends Fragment implements GameList.OnRefreshListener { - private MainActivity mParent; - private RecyclerView mRecyclerView; - private GameListFragment.ViewAdapter mAdapter; - - public GameListFragment(MainActivity parent) { - super(R.layout.fragment_game_list); - this.mParent = parent; - } - - private GameList getGameList() { - return mParent.getGameList(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mAdapter = new GameListFragment.ViewAdapter(mParent, getGameList()); - getGameList().addRefreshListener(this); - - mRecyclerView = view.findViewById(R.id.game_list_view); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.setLayoutManager(new LinearLayoutManager(mParent)); - mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(), - DividerItemDecoration.VERTICAL)); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - getGameList().removeRefreshListener(this); - } - - @Override - public void onGameListRefresh() { - mAdapter.notifyDataSetChanged(); - } - - private static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - private MainActivity mParent; - private View mItemView; - private GameListEntry mEntry; - - public ViewHolder(@NonNull MainActivity parent, @NonNull View itemView) { - super(itemView); - mParent = parent; - mItemView = itemView; - mItemView.setOnClickListener(this); - mItemView.setOnLongClickListener(this); - } - - private String getSubTitle() { - String fileName = FileHelper.getFileNameForPath(mEntry.getPath()); - String sizeString = String.format("%.2f MB", (double) mEntry.getSize() / 1048576.0); - return String.format("%s (%s)", fileName, sizeString); - } - - public void bindToEntry(GameListEntry entry) { - mEntry = entry; - - ((TextView) mItemView.findViewById(R.id.game_list_view_entry_title)).setText(entry.getTitle()); - ((TextView) mItemView.findViewById(R.id.game_list_view_entry_subtitle)).setText(getSubTitle()); - - int regionDrawableId; - switch (entry.getRegion()) { - case NTSC_J: - regionDrawableId = R.drawable.flag_jp; - break; - case PAL: - regionDrawableId = R.drawable.flag_eu; - break; - case Other: - regionDrawableId = R.drawable.ic_baseline_help_24; - break; - case NTSC_U: - default: - regionDrawableId = R.drawable.flag_us; - break; - } - - ((ImageView) mItemView.findViewById(R.id.game_list_view_entry_region_icon)) - .setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), regionDrawableId)); - - int typeDrawableId; - switch (entry.getType()) { - case PSExe: - typeDrawableId = R.drawable.ic_emblem_system; - break; - - case Playlist: - typeDrawableId = R.drawable.ic_baseline_playlist_play_24; - break; - - case PSF: - typeDrawableId = R.drawable.ic_baseline_library_music_24; - break; - - case Disc: - default: - typeDrawableId = R.drawable.ic_media_cdrom; - break; - } - - ImageView icon = ((ImageView) mItemView.findViewById(R.id.game_list_view_entry_type_icon)); - icon.setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), typeDrawableId)); - - final String coverPath = entry.getCoverPath(); - if (coverPath != null) { - new ImageLoadTask(icon).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, coverPath); - } - - int compatibilityDrawableId; - switch (entry.getCompatibilityRating()) { - case DoesntBoot: - compatibilityDrawableId = R.drawable.ic_star_1; - break; - case CrashesInIntro: - compatibilityDrawableId = R.drawable.ic_star_2; - break; - case CrashesInGame: - compatibilityDrawableId = R.drawable.ic_star_3; - break; - case GraphicalAudioIssues: - compatibilityDrawableId = R.drawable.ic_star_4; - break; - case NoIssues: - compatibilityDrawableId = R.drawable.ic_star_5; - break; - case Unknown: - default: - compatibilityDrawableId = R.drawable.ic_star_0; - break; - } - - ((ImageView) mItemView.findViewById(R.id.game_list_view_compatibility_icon)) - .setImageDrawable(ContextCompat.getDrawable(mItemView.getContext(), compatibilityDrawableId)); - } - - @Override - public void onClick(View v) { - mParent.startEmulation(mEntry.getPath(), mParent.shouldResumeStateByDefault()); - } - - @Override - public boolean onLongClick(View v) { - mParent.openGamePopupMenu(v, mEntry); - return true; - } - } - - private static class ViewAdapter extends RecyclerView.Adapter { - private MainActivity mParent; - private LayoutInflater mInflater; - private GameList mGameList; - - public ViewAdapter(@NonNull MainActivity parent, @NonNull GameList gameList) { - mParent = parent; - mInflater = LayoutInflater.from(parent); - mGameList = gameList; - } - - @NonNull - @Override - public GameListFragment.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new GameListFragment.ViewHolder(mParent, mInflater.inflate(R.layout.layout_game_list_entry, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull GameListFragment.ViewHolder holder, int position) { - GameListEntry entry = mGameList.getEntry(position); - holder.bindToEntry(entry); - } - - @Override - public int getItemCount() { - return mGameList.getEntryCount(); - } - - @Override - public int getItemViewType(int position) { - return R.layout.layout_game_list_entry; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GamePropertiesActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/GamePropertiesActivity.java deleted file mode 100644 index 2ca556229..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GamePropertiesActivity.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListAdapter; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.ListFragment; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceScreen; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; - -import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; - -public class GamePropertiesActivity extends AppCompatActivity { - PropertyListAdapter mPropertiesListAdapter; - GameListEntry mGameListEntry; - - public ListAdapter getPropertyListAdapter() { - if (mPropertiesListAdapter != null) - return mPropertiesListAdapter; - - mPropertiesListAdapter = new PropertyListAdapter(this); - mPropertiesListAdapter.addItem("title", "Title", mGameListEntry.getTitle()); - mPropertiesListAdapter.addItem("serial", "Serial", mGameListEntry.getCode()); - mPropertiesListAdapter.addItem("type", "Type", mGameListEntry.getType().toString()); - mPropertiesListAdapter.addItem("path", "Path", mGameListEntry.getPath()); - mPropertiesListAdapter.addItem("region", "Region", mGameListEntry.getRegion().toString()); - mPropertiesListAdapter.addItem("compatibility", "Compatibility Rating", mGameListEntry.getCompatibilityRating().toString()); - return mPropertiesListAdapter; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - - String path = getIntent().getStringExtra("path"); - if (path == null || path.isEmpty()) { - finish(); - return; - } - - mGameListEntry = AndroidHostInterface.getInstance().getGameListEntry(path); - if (mGameListEntry == null) { - finish(); - return; - } - - setContentView(R.layout.settings_activity); - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings, new SettingsCollectionFragment(this)) - .commit(); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - setTitle(mGameListEntry.getTitle()); - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - private void displayError(String text) { - new AlertDialog.Builder(this) - .setTitle(R.string.emulation_activity_error) - .setMessage(text) - .setNegativeButton(R.string.main_activity_ok, ((dialog, which) -> dialog.dismiss())) - .create() - .show(); - } - - private void createBooleanGameSetting(PreferenceScreen ps, String key, int titleId) { - GameSettingPreference pref = new GameSettingPreference(ps.getContext(), mGameListEntry.getPath(), key, titleId); - ps.addPreference(pref); - } - - private void createTraitGameSetting(PreferenceScreen ps, String key, int titleId) { - GameTraitPreference pref = new GameTraitPreference(ps.getContext(), mGameListEntry.getPath(), key, titleId); - ps.addPreference(pref); - } - - private void createListGameSetting(PreferenceScreen ps, String key, int titleId, int entryId, int entryValuesId) { - GameSettingPreference pref = new GameSettingPreference(ps.getContext(), mGameListEntry.getPath(), key, titleId, entryId, entryValuesId); - ps.addPreference(pref); - } - - public static class GameSettingsFragment extends PreferenceFragmentCompat { - private GamePropertiesActivity activity; - - public GameSettingsFragment(GamePropertiesActivity activity) { - this.activity = activity; - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext()); - activity.createListGameSetting(ps, "CPUOverclock", R.string.settings_cpu_overclocking, R.array.settings_advanced_cpu_overclock_entries, R.array.settings_advanced_cpu_overclock_values); - activity.createListGameSetting(ps, "CDROMReadSpeedup", R.string.settings_cdrom_read_speedup, R.array.settings_cdrom_read_speedup_entries, R.array.settings_cdrom_read_speedup_values); - activity.createListGameSetting(ps, "CDROMSeekSpeedup", R.string.settings_cdrom_seek_speedup, R.array.settings_cdrom_seek_speedup_entries, R.array.settings_cdrom_seek_speedup_values); - - activity.createListGameSetting(ps, "GPURenderer", R.string.settings_gpu_renderer, R.array.gpu_renderer_entries, R.array.gpu_renderer_values); - activity.createListGameSetting(ps, "DisplayAspectRatio", R.string.settings_aspect_ratio, R.array.settings_display_aspect_ratio_names, R.array.settings_display_aspect_ratio_values); - activity.createListGameSetting(ps, "DisplayCropMode", R.string.settings_crop_mode, R.array.settings_display_crop_mode_entries, R.array.settings_display_crop_mode_values); - activity.createListGameSetting(ps, "GPUDownsampleMode", R.string.settings_downsample_mode, R.array.settings_downsample_mode_entries, R.array.settings_downsample_mode_values); - activity.createBooleanGameSetting(ps, "DisplayLinearUpscaling", R.string.settings_linear_upscaling); - activity.createBooleanGameSetting(ps, "DisplayIntegerUpscaling", R.string.settings_integer_upscaling); - activity.createBooleanGameSetting(ps, "DisplayForce4_3For24Bit", R.string.settings_force_4_3_for_24bit); - - activity.createListGameSetting(ps, "GPUResolutionScale", R.string.settings_gpu_resolution_scale, R.array.settings_gpu_resolution_scale_entries, R.array.settings_gpu_resolution_scale_values); - activity.createListGameSetting(ps, "GPUMSAA", R.string.settings_msaa, R.array.settings_gpu_msaa_entries, R.array.settings_gpu_msaa_values); - activity.createBooleanGameSetting(ps, "GPUTrueColor", R.string.settings_true_color); - activity.createBooleanGameSetting(ps, "GPUScaledDithering", R.string.settings_scaled_dithering); - activity.createListGameSetting(ps, "GPUTextureFilter", R.string.settings_texture_filtering, R.array.settings_gpu_texture_filter_names, R.array.settings_gpu_texture_filter_values); - activity.createBooleanGameSetting(ps, "GPUForceNTSCTimings", R.string.settings_force_ntsc_timings); - activity.createBooleanGameSetting(ps, "GPUWidescreenHack", R.string.settings_widescreen_hack); - activity.createBooleanGameSetting(ps, "GPUPGXP", R.string.settings_pgxp_geometry_correction); - activity.createBooleanGameSetting(ps, "PGXPPreserveProjFP", R.string.settings_pgxp_preserve_projection_precision); - activity.createBooleanGameSetting(ps, "GPUPGXPDepthBuffer", R.string.settings_pgxp_depth_buffer); - activity.createTraitGameSetting(ps, "ForceSoftwareRenderer", R.string.settings_use_software_renderer); - activity.createTraitGameSetting(ps, "ForceSoftwareRendererForReadbacks", R.string.settings_use_software_renderer_for_readbacks); - activity.createTraitGameSetting(ps, "DisableWidescreen", R.string.settings_disable_widescreen); - activity.createTraitGameSetting(ps, "ForcePGXPVertexCache", R.string.settings_pgxp_vertex_cache); - activity.createTraitGameSetting(ps, "ForcePGXPCPUMode", R.string.settings_pgxp_cpu_mode); - - setPreferenceScreen(ps); - } - } - - public static class ControllerSettingsFragment extends PreferenceFragmentCompat { - private GamePropertiesActivity activity; - - public ControllerSettingsFragment(GamePropertiesActivity activity) { - this.activity = activity; - } - - private void createInputProfileSetting(PreferenceScreen ps) { - final GameSettingPreference pref = new GameSettingPreference(ps.getContext(), activity.mGameListEntry.getPath(), "InputProfileName", R.string.settings_input_profile); - - final String[] inputProfileNames = AndroidHostInterface.getInstance().getInputProfileNames(); - pref.setEntries(inputProfileNames); - pref.setEntryValues(inputProfileNames); - ps.addPreference(pref); - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext()); - - activity.createListGameSetting(ps, "Controller1Type", R.string.settings_controller_type, R.array.settings_controller_type_entries, R.array.settings_controller_type_values); - activity.createListGameSetting(ps, "MemoryCard1Type", R.string.settings_memory_card_1_type, R.array.settings_memory_card_mode_entries, R.array.settings_memory_card_mode_values); - activity.createListGameSetting(ps, "MemoryCard2Type", R.string.settings_memory_card_2_type, R.array.settings_memory_card_mode_entries, R.array.settings_memory_card_mode_values); - createInputProfileSetting(ps); - - setPreferenceScreen(ps); - } - } - - public static class SettingsCollectionFragment extends Fragment { - private GamePropertiesActivity activity; - private SettingsCollectionAdapter adapter; - private ViewPager2 viewPager; - - public SettingsCollectionFragment(GamePropertiesActivity activity) { - this.activity = activity; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_controller_settings, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - adapter = new SettingsCollectionAdapter(activity, this); - viewPager = view.findViewById(R.id.view_pager); - viewPager.setAdapter(adapter); - - TabLayout tabLayout = view.findViewById(R.id.tab_layout); - new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { - switch (position) { - case 0: - tab.setText(R.string.game_properties_tab_summary); - break; - case 1: - tab.setText(R.string.game_properties_tab_game_settings); - break; - case 2: - tab.setText(R.string.game_properties_tab_controller_settings); - break; - } - }).attach(); - } - } - - public static class SettingsCollectionAdapter extends FragmentStateAdapter { - private GamePropertiesActivity activity; - - public SettingsCollectionAdapter(@NonNull GamePropertiesActivity activity, @NonNull Fragment fragment) { - super(fragment); - this.activity = activity; - } - - @NonNull - @Override - public Fragment createFragment(int position) { - switch (position) { - case 0: { // Summary - ListFragment lf = new ListFragment(); - lf.setListAdapter(activity.getPropertyListAdapter()); - return lf; - } - - case 1: { // Game Settings - return new GameSettingsFragment(activity); - } - - case 2: { // Controller Settings - return new ControllerSettingsFragment(activity); - } - - // TODO: Memory Card Editor - - default: - return null; - } - } - - @Override - public int getItemCount() { - return 3; - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameSettingPreference.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameSettingPreference.java deleted file mode 100644 index d86e8af41..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameSettingPreference.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.util.AttributeSet; - -import androidx.preference.ListPreference; - -public class GameSettingPreference extends ListPreference { - private String mGamePath; - - /** - * Creates a boolean game property preference. - */ - public GameSettingPreference(Context context, String gamePath, String settingKey, int titleId) { - super(context); - mGamePath = gamePath; - setPersistent(false); - setTitle(titleId); - setKey(settingKey); - setIconSpaceReserved(false); - setSummaryProvider(SimpleSummaryProvider.getInstance()); - - setEntries(R.array.settings_boolean_entries); - setEntryValues(R.array.settings_boolean_values); - - updateValue(); - } - - /** - * Creates a list game property preference. - */ - public GameSettingPreference(Context context, String gamePath, String settingKey, int titleId, int entryArray, int entryValuesArray) { - super(context); - mGamePath = gamePath; - setPersistent(false); - setTitle(titleId); - setKey(settingKey); - setIconSpaceReserved(false); - setSummaryProvider(SimpleSummaryProvider.getInstance()); - - setEntries(entryArray); - setEntryValues(entryValuesArray); - - updateValue(); - } - - private void updateValue() { - final String value = AndroidHostInterface.getInstance().getGameSettingValue(mGamePath, getKey()); - if (value == null) - super.setValue("null"); - else - super.setValue(value); - } - - @Override - public void setValue(String value) { - super.setValue(value); - if (value.equals("null")) - AndroidHostInterface.getInstance().setGameSettingValue(mGamePath, getKey(), null); - else - AndroidHostInterface.getInstance().setGameSettingValue(mGamePath, getKey(), value); - } - - @Override - public void setEntries(CharSequence[] entries) { - final int length = (entries != null) ? entries.length : 0; - CharSequence[] newEntries = new CharSequence[length + 1]; - newEntries[0] = getContext().getString(R.string.game_properties_preference_use_global_setting); - if (entries != null) - System.arraycopy(entries, 0, newEntries, 1, entries.length); - super.setEntries(newEntries); - } - - @Override - public void setEntryValues(CharSequence[] entryValues) { - final int length = (entryValues != null) ? entryValues.length : 0; - CharSequence[] newEntryValues = new CharSequence[length + 1]; - newEntryValues[0] = "null"; - if (entryValues != null) - System.arraycopy(entryValues, 0, newEntryValues, 1, length); - super.setEntryValues(newEntryValues); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameTraitPreference.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameTraitPreference.java deleted file mode 100644 index a3eb13aab..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameTraitPreference.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; - -import androidx.preference.ListPreference; -import androidx.preference.SwitchPreference; - -public class GameTraitPreference extends SwitchPreference { - private String mGamePath; - - /** - * Creates a boolean game property preference. - */ - public GameTraitPreference(Context context, String gamePath, String settingKey, int titleId) { - super(context); - mGamePath = gamePath; - setPersistent(false); - setTitle(titleId); - setKey(settingKey); - setIconSpaceReserved(false); - updateValue(); - } - - private void updateValue() { - final String value = AndroidHostInterface.getInstance().getGameSettingValue(mGamePath, getKey()); - super.setChecked(value != null && value.equals("true")); - } - - @Override - public void setChecked(boolean checked) { - super.setChecked(checked); - AndroidHostInterface.getInstance().setGameSettingValue(mGamePath, getKey(), checked ? "true" : "false"); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GenerateCoverTask.java b/android/app/src/main/java/com/github/stenzek/duckstation/GenerateCoverTask.java deleted file mode 100644 index f503d4e15..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GenerateCoverTask.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.os.AsyncTask; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.widget.ImageView; - -import java.lang.ref.WeakReference; - -public class GenerateCoverTask extends AsyncTask { - private final Context mContext; - private final WeakReference mView; - private final String mTitle; - - public GenerateCoverTask(Context context, ImageView view, String title) { - mContext = context; - mView = new WeakReference<>(view); - mTitle = title; - } - - @Override - protected Bitmap doInBackground(Void... voids) { - try { - final Bitmap background = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.cover_placeholder); - if (background == null) - return null; - - final Bitmap bitmap = Bitmap.createBitmap(background.getWidth(), background.getHeight(), background.getConfig()); - final Canvas canvas = new Canvas(bitmap); - final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - canvas.drawBitmap(background, 0.0f, 0.0f, paint); - - paint.setColor(Color.rgb(255, 255, 255)); - paint.setTextSize(100); - paint.setShadowLayer(1.0f, 0.0f, 1.0f, Color.DKGRAY); - paint.setTextAlign(Paint.Align.CENTER); - - final StaticLayout staticLayout = new StaticLayout(mTitle, paint, - canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL, 1, 0, false); - canvas.save(); - canvas.translate(canvas.getWidth() / 2, (canvas.getHeight() / 2) - (staticLayout.getHeight() / 2)); - staticLayout.draw(canvas); - canvas.restore(); - return bitmap; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - ImageView iv = mView.get(); - if (iv != null) - iv.setImageBitmap(bitmap); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GridAutofitLayoutManager.java b/android/app/src/main/java/com/github/stenzek/duckstation/GridAutofitLayoutManager.java deleted file mode 100644 index f7e3325c2..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GridAutofitLayoutManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.github.stenzek.duckstation; - -// https://stackoverflow.com/questions/26666143/recyclerview-gridlayoutmanager-how-to-auto-detect-span-count - -import android.content.Context; -import android.util.TypedValue; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -public class GridAutofitLayoutManager extends GridLayoutManager -{ - private int columnWidth; - private boolean isColumnWidthChanged = true; - private int lastWidth; - private int lastHeight; - - public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) { - /* Initially set spanCount to 1, will be changed automatically later. */ - super(context, 1); - setColumnWidth(checkedColumnWidth(context, columnWidth)); - } - - public GridAutofitLayoutManager( - @NonNull final Context context, - final int columnWidth, - final int orientation, - final boolean reverseLayout) { - - /* Initially set spanCount to 1, will be changed automatically later. */ - super(context, 1, orientation, reverseLayout); - setColumnWidth(checkedColumnWidth(context, columnWidth)); - } - - private int checkedColumnWidth(@NonNull final Context context, int columnWidth) { - if (columnWidth <= 0) { - /* Set default columnWidth value (48dp here). It is better to move this constant - to static constant on top, but we need context to convert it to dp, so can't really - do so. */ - columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, - context.getResources().getDisplayMetrics()); - } - return columnWidth; - } - - public void setColumnWidth(final int newColumnWidth) { - if (newColumnWidth > 0 && newColumnWidth != columnWidth) { - columnWidth = newColumnWidth; - isColumnWidthChanged = true; - } - } - - @Override - public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) { - final int width = getWidth(); - final int height = getHeight(); - if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { - final int totalSpace; - if (getOrientation() == VERTICAL) { - totalSpace = width - getPaddingRight() - getPaddingLeft(); - } else { - totalSpace = height - getPaddingTop() - getPaddingBottom(); - } - final int spanCount = Math.max(1, totalSpace / columnWidth); - setSpanCount(spanCount); - isColumnWidthChanged = false; - } - lastWidth = width; - lastHeight = height; - super.onLayoutChildren(recycler, state); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java b/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java deleted file mode 100644 index f16ce9f15..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/HotkeyInfo.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.stenzek.duckstation; - -public class HotkeyInfo { - private String mCategory; - private String mName; - private String mDisplayName; - - public HotkeyInfo(String category, String name, String displayName) { - mCategory = category; - mName = name; - mDisplayName = displayName; - } - - public String getCategory() { - return mCategory; - } - - public String getName() { - return mName; - } - - public String getDisplayName() { - return mDisplayName; - } - - public String getBindingConfigKey() { - return String.format("Hotkeys/%s", mName); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/ImageLoadTask.java b/android/app/src/main/java/com/github/stenzek/duckstation/ImageLoadTask.java deleted file mode 100644 index 447a4c87c..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/ImageLoadTask.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; -import android.widget.ImageView; - -import java.lang.ref.WeakReference; - -public class ImageLoadTask extends AsyncTask { - private WeakReference mView; - - public ImageLoadTask(ImageView view) { - mView = new WeakReference<>(view); - } - - @Override - protected Bitmap doInBackground(String... strings) { - try { - return BitmapFactory.decodeFile(strings[0]); - } catch (Exception e) { - return null; - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - ImageView iv = mView.get(); - if (iv != null) - iv.setImageBitmap(bitmap); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java deleted file mode 100644 index c849fa1d7..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java +++ /dev/null @@ -1,556 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.Manifest; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.util.Log; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ListView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.appcompat.widget.Toolbar; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceManager; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Locale; - -public class MainActivity extends AppCompatActivity { - private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1; - private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2; - private static final int REQUEST_IMPORT_BIOS_IMAGE = 3; - private static final int REQUEST_START_FILE = 4; - private static final int REQUEST_SETTINGS = 5; - private static final int REQUEST_EDIT_GAME_DIRECTORIES = 6; - private static final int REQUEST_CHOOSE_COVER_IMAGE = 7; - - private GameList mGameList; - private ListView mGameListView; - private GameListFragment mGameListFragment; - private GameGridFragment mGameGridFragment; - private Fragment mEmptyGameListFragment; - private boolean mHasExternalStoragePermissions = false; - private boolean mIsShowingGameGrid = false; - private String mPathForChosenCoverImage = null; - - public GameList getGameList() { - return mGameList; - } - - public boolean shouldResumeStateByDefault() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - if (!prefs.getBoolean("Main/SaveStateOnExit", true)) - return false; - - // don't resume with challenge mode on - if (Achievement.willChallengeModeBeEnabled(this)) - return false; - - return true; - } - - private void setLanguage() { - String language = PreferenceManager.getDefaultSharedPreferences(this).getString("Main/Language", "none"); - if (language == null || language.equals("none")) { - return; - } - - String[] parts = language.split("-"); - if (parts.length < 2) - return; - - Locale locale = new Locale(parts[0], parts[1]); - Locale.setDefault(locale); - - Resources res = getResources(); - Configuration config = res.getConfiguration(); - config.setLocale(locale); - res.updateConfiguration(config, res.getDisplayMetrics()); - } - - private void setTheme() { - String theme = PreferenceManager.getDefaultSharedPreferences(this).getString("Main/Theme", "follow_system"); - if (theme == null) - return; - - if (theme.equals("follow_system")) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - } else if (theme.equals("light")) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - } else if (theme.equals("dark")) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - } - } - - private void loadSettings() { - setLanguage(); - setTheme(); - - mIsShowingGameGrid = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("Main/GameGridView", false); - } - - private void switchGameListView() { - mIsShowingGameGrid = !mIsShowingGameGrid; - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putBoolean("Main/GameGridView", mIsShowingGameGrid) - .commit(); - - updateGameListFragment(true); - invalidateOptionsMenu(); - } - - private void updateGameListFragment(boolean allowEmpty) { - final Fragment listFragment = (allowEmpty && mGameList.getEntryCount() == 0) ? - mEmptyGameListFragment : - (mIsShowingGameGrid ? mGameGridFragment : mGameListFragment); - - getSupportFragmentManager() - .beginTransaction() - .setReorderingAllowed(true). - replace(R.id.content_fragment, listFragment) - .commitAllowingStateLoss(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - loadSettings(); - setTitle(null); - - super.onCreate(null); - - setContentView(R.layout.activity_main); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - findViewById(R.id.fab_resume).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startEmulation(null, shouldResumeStateByDefault()); - } - }); - - // Set up game list view. - mGameList = new GameList(this); - mGameList.addRefreshListener(() -> updateGameListFragment(true)); - mGameListFragment = new GameListFragment(this); - mGameGridFragment = new GameGridFragment(this); - mEmptyGameListFragment = new EmptyGameListFragment(this); - updateGameListFragment(false); - - mHasExternalStoragePermissions = checkForExternalStoragePermissions(); - if (mHasExternalStoragePermissions) - completeStartup(); - } - - private void completeStartup() { - if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { - Log.i("MainActivity", "Failed to create host interface"); - throw new RuntimeException("Failed to create host interface"); - } - - AndroidHostInterface.getInstance().setContext(this); - mGameList.refresh(false, false, this); - UpdateNotes.displayUpdateNotes(this); - } - - public void startAddGameDirectory() { - if (!checkForExternalStoragePermissions()) - return; - - Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - i.addCategory(Intent.CATEGORY_DEFAULT); - i.putExtra(Intent.EXTRA_LOCAL_ONLY, true); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - startActivityForResult(Intent.createChooser(i, getString(R.string.main_activity_choose_directory)), - REQUEST_ADD_DIRECTORY_TO_GAME_LIST); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - - final MenuItem switchViewItem = menu.findItem(R.id.action_switch_view); - if (switchViewItem != null) { - switchViewItem.setTitle(mIsShowingGameGrid ? R.string.action_show_game_list : R.string.action_show_game_grid); - switchViewItem.setIcon(mIsShowingGameGrid ? R.drawable.ic_baseline_view_list_24 : R.drawable.ic_baseline_grid_view_24); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_start_bios) { - startEmulation(null, false); - } else if (id == R.id.action_start_file) { - startStartFile(); - } else if (id == R.id.action_edit_game_directories) { - Intent intent = new Intent(this, GameDirectoriesActivity.class); - startActivityForResult(intent, REQUEST_EDIT_GAME_DIRECTORIES); - return true; - } else if (id == R.id.action_scan_for_new_games) { - mGameList.refresh(false, false, this); - } else if (id == R.id.action_rescan_all_games) { - mGameList.refresh(true, true, this); - } else if (id == R.id.action_import_bios) { - importBIOSImage(); - } else if (id == R.id.action_settings) { - Intent intent = new Intent(this, SettingsActivity.class); - startActivityForResult(intent, REQUEST_SETTINGS); - return true; - } else if (id == R.id.action_controller_settings) { - Intent intent = new Intent(this, ControllerSettingsActivity.class); - startActivity(intent); - return true; - } else if (id == R.id.action_memory_card_editor) { - Intent intent = new Intent(this, MemoryCardEditorActivity.class); - startActivity(intent); - return true; - } else if (id == R.id.action_switch_view) { - switchGameListView(); - return true; - } else if (id == R.id.action_show_version) { - showVersion(); - return true; - } else if (id == R.id.action_github_respository) { - openGithubRepository(); - return true; - } else if (id == R.id.action_discord_server) { - openDiscordServer(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - switch (requestCode) { - case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: { - if (resultCode != RESULT_OK || data.getData() == null) - return; - - // Use legacy storage on devices older than Android 11... apparently some of them - // have broken storage access framework.... - if (!GameDirectoriesActivity.useStorageAccessFramework(this)) { - final String path = FileHelper.getFullPathFromTreeUri(data.getData(), this); - if (path != null) { - GameDirectoriesActivity.addSearchDirectory(this, path, true); - mGameList.refresh(false, false, this); - return; - } - } - - try { - getContentResolver().takePersistableUriPermission(data.getData(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } catch (Exception e) { - Toast.makeText(this, "Failed to take persistable permission.", Toast.LENGTH_LONG); - e.printStackTrace(); - } - - GameDirectoriesActivity.addSearchDirectory(this, data.getDataString(), true); - mGameList.refresh(false, false, this); - } - break; - - case REQUEST_IMPORT_BIOS_IMAGE: { - if (resultCode != RESULT_OK) - return; - - onImportBIOSImageResult(data.getData()); - } - break; - - case REQUEST_START_FILE: { - if (resultCode != RESULT_OK || data.getData() == null) - return; - - startEmulation(data.getDataString(), shouldResumeStateByDefault()); - } - break; - - case REQUEST_SETTINGS: { - loadSettings(); - } - break; - - case REQUEST_EDIT_GAME_DIRECTORIES: { - mGameList.refresh(false, false, this); - } - break; - - case REQUEST_CHOOSE_COVER_IMAGE: { - final String gamePath = mPathForChosenCoverImage; - mPathForChosenCoverImage = null; - if (resultCode != RESULT_OK) - return; - - finishChooseCoverImage(gamePath, data.getData()); - } - break; - } - } - - private boolean checkForExternalStoragePermissions() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == - PackageManager.PERMISSION_GRANTED && - ContextCompat - .checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == - PackageManager.PERMISSION_GRANTED) { - return true; - } - - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_EXTERNAL_STORAGE_PERMISSIONS); - return false; - } - - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - // check that all were successful - for (int i = 0; i < grantResults.length; i++) { - if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - if (!mHasExternalStoragePermissions) { - mHasExternalStoragePermissions = true; - completeStartup(); - } - } else { - Toast.makeText(this, - R.string.main_activity_external_storage_permissions_error, - Toast.LENGTH_LONG); - finish(); - } - } - } - - public boolean openGameProperties(String path) { - Intent intent = new Intent(this, GamePropertiesActivity.class); - intent.putExtra("path", path); - startActivity(intent); - return true; - } - - public void openGamePopupMenu(View anchorToView, GameListEntry entry) { - androidx.appcompat.widget.PopupMenu menu = new androidx.appcompat.widget.PopupMenu(this, anchorToView, Gravity.RIGHT | Gravity.TOP); - menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu()); - menu.setOnMenuItemClickListener(item -> { - int id = item.getItemId(); - if (id == R.id.game_list_entry_menu_start_game) { - startEmulation(entry.getPath(), false); - return true; - } else if (id == R.id.game_list_entry_menu_resume_game) { - startEmulation(entry.getPath(), true); - return true; - } else if (id == R.id.game_list_entry_menu_properties) { - openGameProperties(entry.getPath()); - return true; - } else if (id == R.id.game_list_entry_menu_choose_cover_image) { - startChooseCoverImage(entry.getPath()); - return true; - } - return false; - }); - - // disable resume state when challenge mode is on - if (Achievement.willChallengeModeBeEnabled(this)) { - MenuItem item = menu.getMenu().findItem(R.id.game_list_entry_menu_resume_game); - if (item != null) - item.setEnabled(false); - } - - menu.show(); - } - - public boolean startEmulation(String bootPath, boolean resumeState) { - if (!doBIOSCheck()) - return false; - - Intent intent = new Intent(this, EmulationActivity.class); - intent.putExtra("bootPath", bootPath); - intent.putExtra("resumeState", resumeState); - startActivity(intent); - return true; - } - - public void startStartFile() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_START_FILE); - } - - private void startChooseCoverImage(String gamePath) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - mPathForChosenCoverImage = gamePath; - startActivityForResult(Intent.createChooser(intent, getString(R.string.menu_game_list_entry_choose_cover_image)), - REQUEST_CHOOSE_COVER_IMAGE); - } - - private void finishChooseCoverImage(String gamePath, Uri uri) { - final GameListEntry gameListEntry = mGameList.getEntryForPath(gamePath); - if (gameListEntry == null) - return; - - final Bitmap bitmap = FileHelper.loadBitmapFromUri(this, uri); - if (bitmap == null) { - Toast.makeText(this, "Failed to open/decode image.", Toast.LENGTH_LONG).show(); - return; - } - - final String coverFileName = String.format("%s/covers/%s.png", - AndroidHostInterface.getUserDirectory(), gameListEntry.getTitle()); - try { - final File file = new File(coverFileName); - final OutputStream outputStream = new FileOutputStream(file); - final boolean result = bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); - outputStream.close();; - if (!result) { - file.delete(); - throw new Exception("Failed to compress bitmap."); - } - - gameListEntry.setCoverPath(coverFileName); - mGameList.fireRefreshListeners(); - } catch (Exception e) { - e.printStackTrace(); - Toast.makeText(this, "Failed to save image.", Toast.LENGTH_LONG).show(); - } - - bitmap.recycle(); - } - - private boolean doBIOSCheck() { - if (AndroidHostInterface.getInstance().hasAnyBIOSImages()) - return true; - - new AlertDialog.Builder(this) - .setTitle(R.string.main_activity_missing_bios_image) - .setMessage(R.string.main_activity_missing_bios_image_prompt) - .setPositiveButton(R.string.main_activity_yes, (dialog, button) -> importBIOSImage()) - .setNegativeButton(R.string.main_activity_no, (dialog, button) -> { - }) - .create() - .show(); - - return false; - } - - private void importBIOSImage() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_bios_image)), REQUEST_IMPORT_BIOS_IMAGE); - } - - private void onImportBIOSImageResult(Uri uri) { - // This should really be 512K but just in case we wanted to support the other BIOSes in the future... - final int MAX_BIOS_SIZE = 2 * 1024 * 1024; - - InputStream stream = null; - try { - stream = getContentResolver().openInputStream(uri); - } catch (FileNotFoundException e) { - Toast.makeText(this, R.string.main_activity_failed_to_open_bios_image, Toast.LENGTH_LONG); - return; - } - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - byte[] buffer = new byte[512 * 1024]; - int len; - while ((len = stream.read(buffer)) > 0) { - os.write(buffer, 0, len); - if (os.size() > MAX_BIOS_SIZE) { - throw new IOException(getString(R.string.main_activity_bios_image_too_large)); - } - } - } catch (IOException e) { - new AlertDialog.Builder(this) - .setMessage(getString(R.string.main_activity_failed_to_read_bios_image_prefix) + e.getMessage()) - .setPositiveButton(R.string.main_activity_ok, (dialog, button) -> { - }) - .create() - .show(); - return; - } - - String importResult = AndroidHostInterface.getInstance().importBIOSImage(os.toByteArray()); - String message = (importResult == null) ? getString(R.string.main_activity_invalid_error) : ("BIOS '" + importResult + "' imported."); - - new AlertDialog.Builder(this) - .setMessage(message) - .setPositiveButton(R.string.main_activity_ok, (dialog, button) -> { - }) - .create() - .show(); - } - - private void showVersion() { - final String message = AndroidHostInterface.getFullScmVersion(); - new AlertDialog.Builder(this) - .setTitle(R.string.main_activity_show_version_title) - .setMessage(message) - .setPositiveButton(R.string.main_activity_ok, (dialog, button) -> { - }) - .setNeutralButton(R.string.main_activity_copy, (dialog, button) -> { - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - if (clipboard != null) - clipboard.setPrimaryClip(ClipData.newPlainText(getString(R.string.main_activity_show_version_title), message)); - }) - .create() - .show(); - } - - private void openGithubRepository() { - final String url = "https://github.com/stenzek/duckstation"; - final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - } - - private void openDiscordServer() { - final String url = "https://discord.gg/Buktv3t"; - final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java deleted file mode 100644 index dfe1323b3..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardEditorActivity.java +++ /dev/null @@ -1,524 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.AlertDialog; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Bundle; - -import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.app.AppCompatActivity; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; - -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import java.util.ArrayList; - -public class MemoryCardEditorActivity extends AppCompatActivity { - public static final int REQUEST_IMPORT_CARD = 1; - - private final ArrayList cards = new ArrayList<>(); - private CollectionAdapter adapter; - private ViewPager2 viewPager; - private TabLayout tabLayout; - private TabLayoutMediator tabLayoutMediator; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - setContentView(R.layout.activity_memory_card_editor); - - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - adapter = new CollectionAdapter(this); - viewPager = findViewById(R.id.view_pager); - viewPager.setAdapter(adapter); - - tabLayout = findViewById(R.id.tab_layout); - tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, adapter.getTabConfigurationStrategy()); - tabLayoutMediator.attach(); - - findViewById(R.id.open_card).setOnClickListener((v) -> openCard()); - findViewById(R.id.close_card).setOnClickListener((v) -> closeCard()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_memory_card_editor, menu); - - final boolean hasCurrentCard = (getCurrentCard() != null); - menu.findItem(R.id.action_delete_card).setEnabled(hasCurrentCard); - menu.findItem(R.id.action_format_card).setEnabled(hasCurrentCard); - menu.findItem(R.id.action_import_card).setEnabled(hasCurrentCard); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } - - case R.id.action_import_card: { - importCard(); - return true; - } - - case R.id.action_delete_card: { - deleteCard(); - return true; - } - - case R.id.action_format_card: { - formatCard(); - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - private void openCard() { - final Uri[] uris = MemoryCardImage.getCardUris(this); - if (uris == null) { - displayMessage(getString(R.string.memory_card_editor_no_cards_found)); - return; - } - - final String[] uriTitles = new String[uris.length]; - for (int i = 0; i < uris.length; i++) - uriTitles[i] = MemoryCardImage.getTitleForUri(uris[i]); - - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.memory_card_editor_select_card); - builder.setItems(uriTitles, (dialog, which) -> { - final Uri uri = uris[which]; - for (int i = 0; i < cards.size(); i++) { - if (cards.get(i).getUri().equals(uri)) { - displayError(getString(R.string.memory_card_editor_card_already_open)); - tabLayout.getTabAt(i).select(); - return; - } - } - - final MemoryCardImage card = MemoryCardImage.open(MemoryCardEditorActivity.this, uri); - if (card == null) { - displayError(getString(R.string.memory_card_editor_failed_to_open_card)); - return; - } - - cards.add(card); - refreshView(card); - }); - builder.create().show(); - } - - private void closeCard() { - final int index = tabLayout.getSelectedTabPosition(); - if (index < 0) - return; - - cards.remove(index); - refreshView(index); - } - - private void displayMessage(String message) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - } - - private void displayError(String message) { - final AlertDialog.Builder errorBuilder = new AlertDialog.Builder(this); - errorBuilder.setTitle(R.string.memory_card_editor_error); - errorBuilder.setMessage(message); - errorBuilder.setPositiveButton(R.string.main_activity_ok, (dialog1, which1) -> dialog1.dismiss()); - errorBuilder.create().show(); - } - - private void copySave(MemoryCardImage sourceCard, MemoryCardFileInfo sourceFile) { - if (cards.size() < 2) { - displayError(getString(R.string.memory_card_editor_must_have_at_least_two_cards_to_copy)); - return; - } - - if (cards.indexOf(sourceCard) < 0) { - // this shouldn't happen.. - return; - } - - final MemoryCardImage[] destinationCards = new MemoryCardImage[cards.size() - 1]; - final String[] cardTitles = new String[cards.size() - 1]; - for (int i = 0, d = 0; i < cards.size(); i++) { - if (cards.get(i) == sourceCard) - continue; - - destinationCards[d] = cards.get(i); - cardTitles[d] = cards.get(i).getTitle(); - d++; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.memory_card_editor_copy_save_to, sourceFile.getTitle())); - builder.setItems(cardTitles, (dialog, which) -> { - dialog.dismiss(); - - final MemoryCardImage destinationCard = destinationCards[which]; - - byte[] data = null; - if (destinationCard.getFreeBlocks() < sourceFile.getNumBlocks()) { - displayError(getString(R.string.memory_card_editor_copy_insufficient_blocks, sourceFile.getNumBlocks(), - destinationCard.getFreeBlocks())); - } else if (destinationCard.hasFile(sourceFile.getFilename())) { - displayError(getString(R.string.memory_card_editor_copy_already_exists, sourceFile.getFilename())); - } else if ((data = sourceCard.readFile(sourceFile.getFilename())) == null) { - displayError(getString(R.string.memory_card_editor_copy_read_failed, sourceFile.getFilename())); - } else if (!destinationCard.writeFile(sourceFile.getFilename(), data)) { - displayMessage(getString(R.string.memory_card_editor_copy_write_failed, sourceFile.getFilename())); - } else { - displayMessage(getString(R.string.memory_card_editor_copy_success, sourceFile.getFilename(), - destinationCard.getTitle())); - refreshView(destinationCard); - } - }); - builder.create().show(); - } - - private void deleteSave(MemoryCardImage card, MemoryCardFileInfo file) { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getString(R.string.memory_card_editor_delete_confirm, file.getFilename())); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - if (card.deleteFile(file.getFilename())) { - displayMessage(getString(R.string.memory_card_editor_delete_success, file.getFilename())); - refreshView(card); - } else { - displayError(getString(R.string.memory_card_editor_delete_failed, file.getFilename())); - } - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - private void refreshView(int newSelection) { - final int oldPos = viewPager.getCurrentItem(); - tabLayoutMediator.detach(); - viewPager.setAdapter(null); - viewPager.setAdapter(adapter); - tabLayoutMediator.attach(); - - if (cards.isEmpty()) - return; - - if (newSelection < 0 || newSelection >= tabLayout.getTabCount()) { - if (oldPos < cards.size()) - tabLayout.getTabAt(oldPos).select(); - else - tabLayout.getTabAt(cards.size() - 1).select(); - } else { - tabLayout.getTabAt(newSelection).select(); - } - } - - private void refreshView(MemoryCardImage newSelectedCard) { - if (newSelectedCard == null) - refreshView(-1); - else - refreshView(cards.indexOf(newSelectedCard)); - - invalidateOptionsMenu(); - } - - private MemoryCardImage getCurrentCard() { - final int index = tabLayout.getSelectedTabPosition(); - if (index < 0 || index >= cards.size()) - return null; - - return cards.get(index); - } - - private void importCard() { - if (getCurrentCard() == null) { - displayMessage(getString(R.string.memory_card_editor_no_card_selected)); - return; - } - - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.main_activity_choose_disc_image)), REQUEST_IMPORT_CARD); - } - - private void importCard(Uri uri) { - final MemoryCardImage card = getCurrentCard(); - if (card == null) - return; - - final byte[] data = FileHelper.readBytesFromUri(this, uri, 16 * 1024 * 1024); - if (data == null) { - displayError(getString(R.string.memory_card_editor_import_card_read_failed, uri.toString())); - return; - } - - String importFileName = FileHelper.getDocumentNameFromUri(this, uri); - if (importFileName == null) { - importFileName = uri.getPath(); - if (importFileName == null || importFileName.isEmpty()) - importFileName = uri.toString(); - } - - final String captureImportFileName = importFileName; - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getString(R.string.memory_card_editor_import_card_confirm_message, - importFileName, card.getTitle())); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - dialog.dismiss(); - - if (!card.importCard(captureImportFileName, data)) { - displayError(getString(R.string.memory_card_editor_import_failed)); - return; - } - - refreshView(card); - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - private void formatCard() { - final MemoryCardImage card = getCurrentCard(); - if (card == null) { - displayMessage(getString(R.string.memory_card_editor_no_card_selected)); - return; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getString(R.string.memory_card_editor_format_card_confirm_message, card.getTitle())); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - dialog.dismiss(); - - if (!card.format()) { - displayError(getString(R.string.memory_card_editor_format_card_failed, card.getUri().toString())); - return; - } - - displayMessage(getString(R.string.memory_card_editor_format_card_success, card.getUri().toString())); - refreshView(card); - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - private void deleteCard() { - final MemoryCardImage card = getCurrentCard(); - if (card == null) { - displayMessage(getString(R.string.memory_card_editor_no_card_selected)); - return; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getString(R.string.memory_card_editor_delete_card_confirm_message, card.getTitle())); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - dialog.dismiss(); - - if (!card.delete()) { - displayError(getString(R.string.memory_card_editor_delete_card_failed, card.getUri().toString())); - return; - } - - displayMessage(getString(R.string.memory_card_editor_delete_card_success, card.getUri().toString())); - cards.remove(card); - refreshView(-1); - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - switch (requestCode) { - case REQUEST_IMPORT_CARD: { - if (resultCode != RESULT_OK) - return; - - importCard(data.getData()); - } - break; - } - } - - private static class SaveViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private MemoryCardEditorActivity mParent; - private View mItemView; - private MemoryCardImage mCard; - private MemoryCardFileInfo mFile; - - public SaveViewHolder(MemoryCardEditorActivity parent, @NonNull View itemView) { - super(itemView); - mParent = parent; - mItemView = itemView; - mItemView.setOnClickListener(this); - } - - public void bindToEntry(MemoryCardImage card, MemoryCardFileInfo file) { - mCard = card; - mFile = file; - - ((TextView) mItemView.findViewById(R.id.title)).setText(mFile.getTitle()); - ((TextView) mItemView.findViewById(R.id.filename)).setText(mFile.getFilename()); - - final String blocksText = String.format("%d Blocks", mFile.getNumBlocks()); - final String sizeText = String.format("%.1f KB", (float)mFile.getSize() / 1024.0f); - ((TextView) mItemView.findViewById(R.id.block_size)).setText(blocksText); - ((TextView) mItemView.findViewById(R.id.file_size)).setText(sizeText); - - if (mFile.getNumIconFrames() > 0) { - final Bitmap bitmap = mFile.getIconFrameBitmap(0); - if (bitmap != null) { - ((ImageView) mItemView.findViewById(R.id.icon)).setImageBitmap(bitmap); - } - } - } - - @Override - public void onClick(View v) { - final AlertDialog.Builder builder = new AlertDialog.Builder(mItemView.getContext()); - builder.setTitle(mFile.getFilename()); - builder.setItems(R.array.memory_card_editor_save_menu, ((dialog, which) -> { - switch (which) { - // Copy Save - case 0: { - dialog.dismiss(); - mParent.copySave(mCard, mFile); - } - break; - - // Delete Save - case 1: { - dialog.dismiss(); - mParent.deleteSave(mCard, mFile); - } - break; - } - })); - builder.create().show(); - } - } - - private static class SaveViewAdapter extends RecyclerView.Adapter { - private MemoryCardEditorActivity parent; - private MemoryCardImage card; - private MemoryCardFileInfo[] files; - - public SaveViewAdapter(MemoryCardEditorActivity parent, MemoryCardImage card) { - this.parent = parent; - this.card = card; - this.files = card.getFiles(); - } - - @NonNull - @Override - public SaveViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - final View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_memory_card_save, parent, false); - return new SaveViewHolder(this.parent, rootView); - } - - @Override - public void onBindViewHolder(@NonNull SaveViewHolder holder, int position) { - holder.bindToEntry(card, files[position]); - } - - @Override - public int getItemCount() { - return (files != null) ? files.length : 0; - } - - @Override - public int getItemViewType(int position) { - return R.layout.layout_memory_card_save; - } - } - - public static class MemoryCardFileFragment extends Fragment { - private MemoryCardEditorActivity parent; - private MemoryCardImage card; - private SaveViewAdapter adapter; - private RecyclerView recyclerView; - - public MemoryCardFileFragment(MemoryCardEditorActivity parent, MemoryCardImage card) { - this.parent = parent; - this.card = card; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_memory_card_file, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - adapter = new SaveViewAdapter(parent, card); - recyclerView = view.findViewById(R.id.recyclerView); - recyclerView.setAdapter(adapter); - recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext())); - recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), - DividerItemDecoration.VERTICAL)); - } - } - - public static class CollectionAdapter extends FragmentStateAdapter { - private MemoryCardEditorActivity parent; - private final TabLayoutMediator.TabConfigurationStrategy tabConfigurationStrategy = (tab, position) -> { - tab.setText(parent.cards.get(position).getTitle()); - }; - - public CollectionAdapter(MemoryCardEditorActivity parent) { - super(parent); - this.parent = parent; - } - - public TabLayoutMediator.TabConfigurationStrategy getTabConfigurationStrategy() { - return tabConfigurationStrategy; - } - - @NonNull - @Override - public Fragment createFragment(int position) { - return new MemoryCardFileFragment(parent, parent.cards.get(position)); - } - - @Override - public int getItemCount() { - return parent.cards.size(); - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardFileInfo.java b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardFileInfo.java deleted file mode 100644 index e520c2db5..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardFileInfo.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.graphics.Bitmap; - -import java.nio.ByteBuffer; - -public class MemoryCardFileInfo { - public static final int ICON_WIDTH = 16; - public static final int ICON_HEIGHT = 16; - - private final String filename; - private final String title; - private final int size; - private final int firstBlock; - private final int numBlocks; - private final byte[][] iconFrames; - - public MemoryCardFileInfo(String filename, String title, int size, int firstBlock, int numBlocks, byte[][] iconFrames) { - this.filename = filename; - this.title = title; - this.size = size; - this.firstBlock = firstBlock; - this.numBlocks = numBlocks; - this.iconFrames = iconFrames; - } - - public String getFilename() { - return filename; - } - - public String getTitle() { - return title; - } - - public int getSize() { - return size; - } - - public int getFirstBlock() { - return firstBlock; - } - - public int getNumBlocks() { - return numBlocks; - } - - public int getNumIconFrames() { - return (iconFrames != null) ? iconFrames.length : 0; - } - - public byte[] getIconFrame(int index) { - return iconFrames[index]; - } - - public Bitmap getIconFrameBitmap(int index) { - final byte[] data = getIconFrame(index); - if (data == null) - return null; - - final Bitmap bitmap = Bitmap.createBitmap(ICON_WIDTH, ICON_HEIGHT, Bitmap.Config.ARGB_8888); - bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data)); - return bitmap; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java b/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java deleted file mode 100644 index 5417ecc3d..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MemoryCardImage.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.DocumentsContract; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; - -public class MemoryCardImage { - public static final int DATA_SIZE = 128 * 1024; - public static final String FILE_EXTENSION = ".mcd"; - - private final Context context; - private final Uri uri; - private final byte[] data; - - private MemoryCardImage(Context context, Uri uri, byte[] data) { - this.context = context; - this.uri = uri; - this.data = data; - } - - public static String getTitleForUri(Uri uri) { - String name = uri.getLastPathSegment(); - if (name != null) { - final int lastSlash = name.lastIndexOf('/'); - if (lastSlash >= 0) - name = name.substring(lastSlash + 1); - - if (name.endsWith(FILE_EXTENSION)) - name = name.substring(0, name.length() - FILE_EXTENSION.length()); - } else { - name = uri.toString(); - } - - return name; - } - - public static MemoryCardImage open(Context context, Uri uri) { - byte[] data = FileHelper.readBytesFromUri(context, uri, DATA_SIZE); - if (data == null) - return null; - - if (!isValid(data)) - return null; - - return new MemoryCardImage(context, uri, data); - } - - public static MemoryCardImage create(Context context, Uri uri) { - byte[] data = new byte[DATA_SIZE]; - format(data); - - MemoryCardImage card = new MemoryCardImage(context, uri, data); - if (!card.save()) - return null; - - return card; - } - - public static Uri[] getCardUris(Context context) { - final String directory = String.format("%s/memcards", - AndroidHostInterface.getUserDirectory()); - final ArrayList results = new ArrayList<>(); - - if (directory.charAt(0) == '/') { - // native path - final File directoryFile = new File(directory); - final File[] files = directoryFile.listFiles(); - for (File file : files) { - if (!file.isFile()) - continue; - - if (!file.getName().endsWith(FILE_EXTENSION)) - continue; - - results.add(Uri.fromFile(file)); - } - } else { - try { - final Uri baseUri = null; - final String[] scanProjection = new String[]{ - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE}; - final ContentResolver resolver = context.getContentResolver(); - final String treeDocId = DocumentsContract.getTreeDocumentId(baseUri); - final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(baseUri, treeDocId); - final Cursor cursor = resolver.query(queryUri, scanProjection, null, null, null); - - while (cursor.moveToNext()) { - try { - final String mimeType = cursor.getString(2); - final String documentId = cursor.getString(0); - final Uri uri = DocumentsContract.buildDocumentUriUsingTree(baseUri, documentId); - if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) { - continue; - } - - final String uriString = uri.toString(); - if (!uriString.endsWith(FILE_EXTENSION)) - continue; - - results.add(uri); - } catch (Exception e) { - e.printStackTrace(); - } - } - cursor.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (results.isEmpty()) - return null; - - Collections.sort(results, (a, b) -> a.compareTo(b)); - - final Uri[] resultArray = new Uri[results.size()]; - results.toArray(resultArray); - return resultArray; - } - - private static native boolean isValid(byte[] data); - - private static native void format(byte[] data); - - private static native int getFreeBlocks(byte[] data); - - private static native MemoryCardFileInfo[] getFiles(byte[] data); - - private static native boolean hasFile(byte[] data, String filename); - - private static native byte[] readFile(byte[] data, String filename); - - private static native boolean writeFile(byte[] data, String filename, byte[] fileData); - - private static native boolean deleteFile(byte[] data, String filename); - - private static native boolean importCard(byte[] data, String filename, byte[] importData); - - public boolean save() { - return FileHelper.writeBytesToUri(context, uri, data); - } - - public boolean delete() { - return FileHelper.deleteFileAtUri(context, uri); - } - - public boolean format() { - format(data); - return save(); - } - - public Uri getUri() { - return uri; - } - - public String getTitle() { - return getTitleForUri(uri); - } - - public int getFreeBlocks() { - return getFreeBlocks(data); - } - - public MemoryCardFileInfo[] getFiles() { - return getFiles(data); - } - - public boolean hasFile(String filename) { - return hasFile(data, filename); - } - - public byte[] readFile(String filename) { - return readFile(data, filename); - } - - public boolean writeFile(String filename, byte[] fileData) { - if (!writeFile(data, filename, fileData)) - return false; - - return save(); - } - - public boolean deleteFile(String filename) { - if (!deleteFile(data, filename)) - return false; - - return save(); - } - - public boolean importCard(String filename, byte[] importData) { - if (!importCard(data, filename, importData)) - return false; - - return save(); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/PatchCode.java b/android/app/src/main/java/com/github/stenzek/duckstation/PatchCode.java deleted file mode 100644 index c8de67096..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/PatchCode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.stenzek.duckstation; - -public class PatchCode { - private static final String UNGROUPED_NAME = "Ungrouped"; - - private int mIndex; - private String mGroup; - private String mDescription; - private boolean mEnabled; - - public PatchCode(int index, String group, String description, boolean enabled) { - mIndex = index; - mGroup = group; - mDescription = description; - mEnabled = enabled; - } - - public int getIndex() { - return mIndex; - } - - public String getGroup() { - return mGroup; - } - - public String getDescription() { - return mDescription; - } - - public boolean isEnabled() { - return mEnabled; - } - - public String getDisplayText() { - if (mGroup == null || mGroup.equals(UNGROUPED_NAME)) - return mDescription; - else - return String.format("(%s) %s", mGroup, mDescription); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/PreferenceHelpers.java b/android/app/src/main/java/com/github/stenzek/duckstation/PreferenceHelpers.java deleted file mode 100644 index 99e9ad815..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/PreferenceHelpers.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.SharedPreferences; -import android.util.ArraySet; - -import java.util.Set; - -public class PreferenceHelpers { - /** - * Clears all preferences in the specified section (starting with sectionName/). - * We really don't want to have to do this with JNI... - * - * @param prefs Preferences object. - * @param sectionName Section to clear keys for. - */ - public static void clearSection(SharedPreferences prefs, String sectionName) { - String testSectionName = sectionName + "/"; - SharedPreferences.Editor editor = prefs.edit(); - for (String keyName : prefs.getAll().keySet()) { - if (keyName.startsWith(testSectionName)) { - editor.remove(keyName); - } - } - - editor.commit(); - } - - public static Set getStringSet(SharedPreferences prefs, String keyName) { - Set values = null; - try { - values = prefs.getStringSet(keyName, null); - } catch (Exception e) { - try { - String singleValue = prefs.getString(keyName, null); - if (singleValue != null) { - values = new ArraySet<>(); - values.add(singleValue); - } - } catch (Exception e2) { - - } - } - - return values; - } - - public static boolean addToStringList(SharedPreferences prefs, String keyName, String valueToAdd) { - Set values = getStringSet(prefs, keyName); - if (values == null) { - values = new ArraySet<>(); - } else { - // We need to copy it otherwise the put doesn't save. - Set valuesCopy = new ArraySet<>(); - valuesCopy.addAll(values); - values = valuesCopy; - } - - final boolean result = values.add(valueToAdd); - prefs.edit().putStringSet(keyName, values).commit(); - return result; - } - - public static boolean removeFromStringList(SharedPreferences prefs, String keyName, String valueToRemove) { - Set values = getStringSet(prefs, keyName); - if (values == null) - return false; - - // We need to copy it otherwise the put doesn't save. - Set valuesCopy = new ArraySet<>(); - valuesCopy.addAll(values); - values = valuesCopy; - - final boolean result = values.remove(valueToRemove); - prefs.edit().putStringSet(keyName, values).commit(); - return result; - } - - public static void setStringList(SharedPreferences prefs, String keyName, String[] values) { - Set valueSet = new ArraySet(); - for (String value : values) - valueSet.add(value); - - prefs.edit().putStringSet(keyName, valueSet).commit(); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/PropertyListAdapter.java b/android/app/src/main/java/com/github/stenzek/duckstation/PropertyListAdapter.java deleted file mode 100644 index 3f9a01565..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/PropertyListAdapter.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import java.util.ArrayList; - -public class PropertyListAdapter extends BaseAdapter { - private class Item { - public String key; - public String title; - public String value; - - Item(String key, String title, String value) { - this.key = key; - this.title = title; - this.value = value; - } - } - - private Context mContext; - private ArrayList mItems = new ArrayList<>(); - - public PropertyListAdapter(Context context) { - mContext = context; - } - - public Item getItemByKey(String key) { - for (Item it : mItems) { - if (it.key.equals(key)) - return it; - } - - return null; - } - - public int addItem(String key, String title, String value) { - if (getItemByKey(key) != null) - return -1; - - Item it = new Item(key, title, value); - int position = mItems.size(); - mItems.add(it); - return position; - } - - public boolean removeItem(Item item) { - return mItems.remove(item); - } - - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public Object getItem(int position) { - return mItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = LayoutInflater.from(mContext) - .inflate(R.layout.layout_game_property_entry, parent, false); - } - - TextView titleView = (TextView) convertView.findViewById(R.id.property_title); - TextView valueView = (TextView) convertView.findViewById(R.id.property_value); - Item prop = mItems.get(position); - titleView.setText(prop.title); - valueView.setText(prop.value); - return convertView; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/RatioPreference.java b/android/app/src/main/java/com/github/stenzek/duckstation/RatioPreference.java deleted file mode 100644 index fe6fc5b41..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/RatioPreference.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.preference.Preference; -import androidx.preference.PreferenceDataStore; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceViewHolder; - -public class RatioPreference extends Preference { - private String mNumeratorKey; - private String mDenominatorKey; - - private int mNumeratorValue = 1; - private int mDenominatorValue = 1; - - private int mMinimumNumerator = 1; - private int mMaximumNumerator = 50; - private int mDefaultNumerator = 1; - private int mMinimumDenominator = 1; - private int mMaximumDenominator = 50; - private int mDefaultDenominator = 1; - - private void initAttributes(AttributeSet attrs) { - for (int i = 0; i < attrs.getAttributeCount(); i++) { - final String key = attrs.getAttributeName(i); - if (key.equals("numeratorKey")) { - mNumeratorKey = attrs.getAttributeValue(i); - } else if (key.equals("minimumNumerator")) { - mMinimumNumerator = attrs.getAttributeIntValue(i, 1); - } else if (key.equals("maximumNumerator")) { - mMaximumNumerator = attrs.getAttributeIntValue(i, 1); - } else if (key.equals("defaultNumerator")) { - mDefaultNumerator = attrs.getAttributeIntValue(i, 1); - } else if(key.equals("denominatorKey")) { - mDenominatorKey = attrs.getAttributeValue(i); - } else if (key.equals("minimumDenominator")) { - mMinimumDenominator = attrs.getAttributeIntValue(i, 1); - } else if (key.equals("maximumDenominator")) { - mMaximumDenominator = attrs.getAttributeIntValue(i, 1); - } else if (key.equals("defaultDenominator")) { - mDefaultDenominator = attrs.getAttributeIntValue(i, 1); - } - } - } - - public RatioPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setWidgetLayoutResource(R.layout.layout_ratio_preference); - initAttributes(attrs); - } - - public RatioPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setWidgetLayoutResource(R.layout.layout_ratio_preference); - initAttributes(attrs); - } - - public RatioPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setWidgetLayoutResource(R.layout.layout_ratio_preference); - initAttributes(attrs); - } - - public RatioPreference(Context context) { - super(context); - setWidgetLayoutResource(R.layout.layout_ratio_preference); - } - - private void persistValues() { - final PreferenceDataStore dataStore = getPreferenceDataStore(); - if (dataStore != null) { - if (mNumeratorKey != null) - dataStore.putInt(mNumeratorKey, mNumeratorValue); - if (mDenominatorKey != null) - dataStore.putInt(mDenominatorKey, mDenominatorValue); - } else { - SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit(); - if (mNumeratorKey != null) - editor.putInt(mNumeratorKey, mNumeratorValue); - if (mDenominatorKey != null) - editor.putInt(mDenominatorKey, mDenominatorValue); - editor.commit(); - } - } - - @Override - protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { - super.onAttachedToHierarchy(preferenceManager); - setInitialValue(); - } - - private void setInitialValue() { - final PreferenceDataStore dataStore = getPreferenceDataStore(); - if (dataStore != null) { - if (mNumeratorKey != null) - mNumeratorValue = dataStore.getInt(mNumeratorKey, mDefaultNumerator); - if (mDenominatorKey != null) - mDenominatorValue = dataStore.getInt(mDenominatorKey, mDefaultDenominator); - } else { - final SharedPreferences pm = getPreferenceManager().getSharedPreferences(); - if (mNumeratorKey != null) - mNumeratorValue = pm.getInt(mNumeratorKey, mDefaultNumerator); - if (mDenominatorKey != null) - mDenominatorValue = pm.getInt(mDenominatorKey, mDefaultDenominator); - } - } - - private static BaseAdapter generateDropdownItems(int min, int max) { - return new BaseAdapter() { - @Override - public int getCount() { - return (max - min) + 1; - } - - @Override - public Object getItem(int position) { - return Integer.toString(min + position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView view; - if (convertView != null) { - view = (TextView) convertView; - } else { - view = new TextView(parent.getContext()); - - Resources r = parent.getResources(); - float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0f, r.getDisplayMetrics()); - view.setPadding((int) px, (int) px, (int) px, (int) px); - } - - view.setText(Integer.toString(min + position)); - return view; - } - }; - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - holder.itemView.setClickable(false); - - Spinner numeratorSpinner = (Spinner) holder.findViewById(R.id.numerator); - numeratorSpinner.setAdapter(generateDropdownItems(mMinimumNumerator, mMaximumNumerator)); - numeratorSpinner.setSelection(mNumeratorValue - mMinimumNumerator); - numeratorSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - final int newValue = position + mMinimumNumerator; - if (newValue != mNumeratorValue) { - mNumeratorValue = newValue; - persistValues(); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - Spinner denominatorSpinner = (Spinner) holder.findViewById(R.id.denominator); - denominatorSpinner.setAdapter(generateDropdownItems(mMinimumDenominator, mMaximumDenominator)); - denominatorSpinner.setSelection(mDenominatorValue - mMinimumDenominator); - denominatorSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - final int newValue = position + mMinimumDenominator; - if (newValue != mDenominatorValue) { - mDenominatorValue = newValue; - persistValues(); - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - } - - -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/SaveStateInfo.java b/android/app/src/main/java/com/github/stenzek/duckstation/SaveStateInfo.java deleted file mode 100644 index 7acd33f32..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/SaveStateInfo.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.graphics.Bitmap; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import java.nio.ByteBuffer; - -public class SaveStateInfo { - private String mPath; - private String mGameTitle; - private String mGameCode; - private String mMediaPath; - private String mTimestamp; - private int mSlot; - private boolean mGlobal; - private Bitmap mScreenshot; - - public SaveStateInfo(String path, String gameTitle, String gameCode, String mediaPath, String timestamp, int slot, boolean global, - int screenshotWidth, int screenshotHeight, byte[] screenshotData) { - mPath = path; - mGameTitle = gameTitle; - mGameCode = gameCode; - mMediaPath = mediaPath; - mTimestamp = timestamp; - mSlot = slot; - mGlobal = global; - - if (screenshotData != null) { - try { - mScreenshot = Bitmap.createBitmap(screenshotWidth, screenshotHeight, Bitmap.Config.ARGB_8888); - mScreenshot.copyPixelsFromBuffer(ByteBuffer.wrap(screenshotData)); - } catch (Exception e) { - mScreenshot = null; - } - } - } - - public boolean exists() { - return mPath != null; - } - - public String getPath() { - return mPath; - } - - public String getGameTitle() { - return mGameTitle; - } - - public String getGameCode() { - return mGameCode; - } - - public String getMediaPath() { - return mMediaPath; - } - - public String getTimestamp() { - return mTimestamp; - } - - public int getSlot() { - return mSlot; - } - - public boolean isGlobal() { - return mGlobal; - } - - public Bitmap getScreenshot() { - return mScreenshot; - } - - private void fillView(Context context, View view) { - ImageView imageView = (ImageView) view.findViewById(R.id.image); - TextView summaryView = (TextView) view.findViewById(R.id.summary); - TextView gameView = (TextView) view.findViewById(R.id.game); - TextView pathView = (TextView) view.findViewById(R.id.path); - TextView timestampView = (TextView) view.findViewById(R.id.timestamp); - - if (mScreenshot != null) - imageView.setImageBitmap(mScreenshot); - else - imageView.setImageDrawable(context.getDrawable(R.drawable.ic_baseline_not_interested_60)); - - String summaryText; - if (mGlobal) - summaryView.setText(String.format(context.getString(R.string.save_state_info_global_save_n), mSlot)); - else if (mSlot == 0) - summaryView.setText(R.string.save_state_info_quick_save); - else - summaryView.setText(String.format(context.getString(R.string.save_state_info_game_save_n), mSlot)); - - if (exists()) { - gameView.setText(String.format("%s - %s", mGameCode, mGameTitle)); - - int lastSlashPosition = mMediaPath.lastIndexOf('/'); - if (lastSlashPosition >= 0) - pathView.setText(mMediaPath.substring(lastSlashPosition + 1)); - else - pathView.setText(mMediaPath); - - timestampView.setText(mTimestamp); - } else { - gameView.setText(R.string.save_state_info_slot_is_empty); - pathView.setText(""); - timestampView.setText(""); - } - } - - public static class ListAdapter extends BaseAdapter { - private final Context mContext; - private final SaveStateInfo[] mInfos; - - public ListAdapter(Context context, SaveStateInfo[] infos) { - mContext = context; - mInfos = infos; - } - - @Override - public int getCount() { - return mInfos.length; - } - - @Override - public Object getItem(int position) { - return mInfos[position]; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = LayoutInflater.from(mContext).inflate(R.layout.save_state_view_entry, parent, false); - } - - mInfos[position].fillView(mContext, convertView); - return convertView; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/SettingsActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/SettingsActivity.java deleted file mode 100644 index 47a9313c9..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/SettingsActivity.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.Bundle; -import android.view.MenuItem; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceFragmentCompat; -import androidx.viewpager2.adapter.FragmentStateAdapter; - -public class SettingsActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - setContentView(R.layout.settings_activity); - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings, new SettingsCollectionFragment()) - .commit(); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } - - return super.onOptionsItemSelected(item); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/SettingsCollectionFragment.java b/android/app/src/main/java/com/github/stenzek/duckstation/SettingsCollectionFragment.java deleted file mode 100644 index b5aa1138b..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/SettingsCollectionFragment.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceFragmentCompat; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import androidx.viewpager2.widget.ViewPager2; - -import com.google.android.material.tabs.TabLayout; -import com.google.android.material.tabs.TabLayoutMediator; - -public class SettingsCollectionFragment extends Fragment { - private SettingsCollectionAdapter adapter; - private ViewPager2 viewPager; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_settings_collection, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - adapter = new SettingsCollectionAdapter(this); - viewPager = view.findViewById(R.id.view_pager); - viewPager.setAdapter(adapter); - - TabLayout tabLayout = view.findViewById(R.id.tab_layout); - new TabLayoutMediator(tabLayout, viewPager, - (tab, position) -> tab.setText(getResources().getStringArray(R.array.settings_tabs)[position]) - ).attach(); - } - - public static class SettingsFragment extends PreferenceFragmentCompat { - private final int resourceId; - - public SettingsFragment(int resourceId) { - this.resourceId = resourceId; - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(resourceId, rootKey); - } - } - - public static class SettingsCollectionAdapter extends FragmentStateAdapter { - public SettingsCollectionAdapter(@NonNull Fragment fragment) { - super(fragment); - } - - @NonNull - @Override - public Fragment createFragment(int position) { - switch (position) { - case 0: // General - return new SettingsFragment(R.xml.general_preferences); - - case 1: // Display - return new SettingsFragment(R.xml.display_preferences); - - case 2: // Audio - return new SettingsFragment(R.xml.audio_preferences); - - case 3: // Enhancements - return new SettingsFragment(R.xml.enhancements_preferences); - - case 4: // Achievements - return new AchievementSettingsFragment(); - - case 5: // Advanced - return new SettingsFragment(R.xml.advanced_preferences); - - default: - return new Fragment(); - } - } - - @Override - public int getItemCount() { - return 6; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java deleted file mode 100644 index eca099791..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerAxisView.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; - -public final class TouchscreenControllerAxisView extends View { - private Drawable mBaseDrawable; - private Drawable mStickUnpressedDrawable; - private Drawable mStickPressedDrawable; - private boolean mPressed = false; - private int mPointerId = 0; - private float mXValue = 0.0f; - private float mYValue = 0.0f; - private int mDrawXPos = 0; - private int mDrawYPos = 0; - - private String mConfigName; - private boolean mDefaultVisibility = true; - - private int mControllerIndex = -1; - private int mXAxisCode = -1; - private int mYAxisCode = -1; - private int mLeftButtonCode = -1; - private int mRightButtonCode = -1; - private int mUpButtonCode = -1; - private int mDownButtonCode = -1; - - public TouchscreenControllerAxisView(Context context) { - super(context); - init(); - } - - public TouchscreenControllerAxisView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public TouchscreenControllerAxisView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - private void init() { - mBaseDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_base); - mBaseDrawable.setCallback(this); - mStickUnpressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_unpressed); - mStickUnpressedDrawable.setCallback(this); - mStickPressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_pressed); - mStickPressedDrawable.setCallback(this); - } - - public String getConfigName() { - return mConfigName; - } - public void setConfigName(String configName) { - mConfigName = configName; - } - - public boolean getDefaultVisibility() { return mDefaultVisibility; } - public void setDefaultVisibility(boolean visibility) { mDefaultVisibility = visibility; } - - public void setControllerAxis(int controllerIndex, int xCode, int yCode) { - mControllerIndex = controllerIndex; - mXAxisCode = xCode; - mYAxisCode = yCode; - mLeftButtonCode = -1; - mRightButtonCode = -1; - mUpButtonCode = -1; - mDownButtonCode = -1; - } - - public void setControllerButtons(int controllerIndex, int leftCode, int rightCode, int upCode, int downCode) { - mControllerIndex = controllerIndex; - mXAxisCode = -1; - mYAxisCode = -1; - mLeftButtonCode = leftCode; - mRightButtonCode = rightCode; - mUpButtonCode = upCode; - mDownButtonCode = downCode; - } - - public void setUnpressed() { - if (!mPressed && mXValue == 0.0f && mYValue == 0.0f) - return; - - mPressed = false; - mXValue = 0.0f; - mYValue = 0.0f; - mDrawXPos = 0; - mDrawYPos = 0; - invalidate(); - updateControllerState(); - } - - public void setPressed(int pointerId, float pointerX, float pointerY) { - final float dx = (pointerX / getScaleX()) - (float) (getWidth() / 2); - final float dy = (pointerY / getScaleY()) - (float) (getHeight() / 2); - // Log.i("SetPressed", String.format("px=%f,py=%f dx=%f,dy=%f", pointerX, pointerY, dx, dy)); - - final float pointerDistance = Math.max(Math.abs(dx), Math.abs(dy)); - final float angle = (float) Math.atan2((double) dy, (double) dx); - - final float maxDistance = (float) Math.min((getWidth() - getPaddingLeft() - getPaddingRight()) / 2, (getHeight() - getPaddingTop() - getPaddingBottom()) / 2); - final float length = Math.min(pointerDistance / maxDistance, 1.0f); - // Log.i("SetPressed", String.format("pointerDist=%f,angle=%f,w=%d,h=%d,maxDist=%f,length=%f", pointerDistance, angle, getWidth(), getHeight(), maxDistance, length)); - - final float xValue = (float) Math.cos((double) angle) * length; - final float yValue = (float) Math.sin((double) angle) * length; - mDrawXPos = (int) (xValue * maxDistance); - mDrawYPos = (int) (yValue * maxDistance); - - boolean doUpdate = (pointerId != mPointerId || !mPressed || (xValue != mXValue || yValue != mYValue)); - mPointerId = pointerId; - mPressed = true; - mXValue = xValue; - mYValue = yValue; - // Log.i("SetPressed", String.format("xval=%f,yval=%f,drawX=%d,drawY=%d", mXValue, mYValue, mDrawXPos, mDrawYPos)); - - if (doUpdate) { - invalidate(); - updateControllerState(); - } - } - - private void updateControllerState() { - final float BUTTON_THRESHOLD = 0.33f; - - AndroidHostInterface hostInterface = AndroidHostInterface.getInstance(); - if (mXAxisCode >= 0) - hostInterface.setControllerAxisState(mControllerIndex, mXAxisCode, mXValue); - if (mYAxisCode >= 0) - hostInterface.setControllerAxisState(mControllerIndex, mYAxisCode, mYValue); - - if (mLeftButtonCode >= 0) - hostInterface.setControllerButtonState(mControllerIndex, mLeftButtonCode, (mXValue <= -BUTTON_THRESHOLD)); - if (mRightButtonCode >= 0) - hostInterface.setControllerButtonState(mControllerIndex, mRightButtonCode, (mXValue >= BUTTON_THRESHOLD)); - if (mUpButtonCode >= 0) - hostInterface.setControllerButtonState(mControllerIndex, mUpButtonCode, (mYValue <= -BUTTON_THRESHOLD)); - if (mDownButtonCode >= 0) - hostInterface.setControllerButtonState(mControllerIndex, mDownButtonCode, (mYValue >= BUTTON_THRESHOLD)); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - final int paddingLeft = getPaddingLeft(); - final int paddingTop = getPaddingTop(); - final int paddingRight = getPaddingRight(); - final int paddingBottom = getPaddingBottom(); - final int contentWidth = getWidth() - paddingLeft - paddingRight; - final int contentHeight = getHeight() - paddingTop - paddingBottom; - - mBaseDrawable.setBounds(paddingLeft, paddingTop, - paddingLeft + contentWidth, paddingTop + contentHeight); - mBaseDrawable.draw(canvas); - - final int stickWidth = contentWidth / 3; - final int stickHeight = contentHeight / 3; - final int halfStickWidth = stickWidth / 2; - final int halfStickHeight = stickHeight / 2; - final int centerX = getWidth() / 2; - final int centerY = getHeight() / 2; - final int drawX = centerX + mDrawXPos; - final int drawY = centerY + mDrawYPos; - - Drawable stickDrawable = mPressed ? mStickPressedDrawable : mStickUnpressedDrawable; - stickDrawable.setBounds(drawX - halfStickWidth, drawY - halfStickHeight, drawX + halfStickWidth, drawY + halfStickHeight); - stickDrawable.draw(canvas); - } - - public boolean isPressed() { - return mPressed; - } - - public boolean hasPointerId() { - return mPointerId >= 0; - } - - public int getPointerId() { - return mPointerId; - } - - public void setPointerId(int mPointerId) { - this.mPointerId = mPointerId; - } -} 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 deleted file mode 100644 index b8f70a8a5..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.HapticFeedbackConstants; -import android.view.View; - -/** - * TODO: document your custom view class. - */ -public final class TouchscreenControllerButtonView extends View { - public enum Hotkey - { - NONE, - FAST_FORWARD, - ANALOG_TOGGLE, - OPEN_PAUSE_MENU, - QUICK_LOAD, - QUICK_SAVE - } - - private Drawable mUnpressedDrawable; - private Drawable mPressedDrawable; - private boolean mPressed = false; - private boolean mHapticFeedback = false; - private int mControllerIndex = -1; - private int mButtonCode = -1; - private int mAutoFireSlot = -1; - private Hotkey mHotkey = Hotkey.NONE; - private String mConfigName; - private boolean mDefaultVisibility = true; - private boolean mIsGlidable = true; - - public TouchscreenControllerButtonView(Context context) { - super(context); - init(context, null, 0); - } - - public TouchscreenControllerButtonView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs, defStyle); - } - - private void init(Context context, AttributeSet attrs, int defStyle) { - // Load attributes - final TypedArray a = getContext().obtainStyledAttributes( - attrs, R.styleable.TouchscreenControllerButtonView, defStyle, 0); - - if (a.hasValue(R.styleable.TouchscreenControllerButtonView_unpressedDrawable)) { - mUnpressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_unpressedDrawable); - mUnpressedDrawable.setCallback(this); - } - - if (a.hasValue(R.styleable.TouchscreenControllerButtonView_pressedDrawable)) { - mPressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_pressedDrawable); - mPressedDrawable.setCallback(this); - } - - a.recycle(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - int leftBounds = 0; - int rightBounds = leftBounds + getWidth(); - int topBounds = 0; - int bottomBounds = topBounds + getHeight(); - - if (mPressed) { - final int expandSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 10.0f, getResources().getDisplayMetrics()); - leftBounds -= expandSize; - rightBounds += expandSize; - topBounds -= expandSize; - bottomBounds += expandSize; - } - - // Draw the example drawable on top of the text. - Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable; - if (drawable != null) { - drawable.setBounds(leftBounds, topBounds, rightBounds, bottomBounds); - drawable.draw(canvas); - } - } - - public boolean isPressed() { - return mPressed; - } - - public void setPressed(boolean pressed) { - if (pressed == mPressed) - return; - - mPressed = pressed; - invalidate(); - updateControllerState(); - - if (mHapticFeedback) { - performHapticFeedback(pressed ? HapticFeedbackConstants.VIRTUAL_KEY : HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); - } - } - - public void setButtonCode(int controllerIndex, int code) { - mControllerIndex = controllerIndex; - mButtonCode = code; - } - - public void setAutoFireSlot(int controllerIndex, int slot) { - mControllerIndex = controllerIndex; - mAutoFireSlot = slot; - } - - public void setHotkey(Hotkey hotkey) { - mHotkey = hotkey; - } - - public String getConfigName() { - return mConfigName; - } - public void setConfigName(String name) { - mConfigName = name; - } - - public boolean getIsGlidable() { return mIsGlidable; } - public void setIsGlidable(boolean isGlidable) { mIsGlidable = isGlidable; } - - public boolean getDefaultVisibility() { return mDefaultVisibility; } - public void setDefaultVisibility(boolean visibility) { mDefaultVisibility = visibility; } - - public void setHapticFeedback(boolean enabled) { - mHapticFeedback = enabled; - } - - private void updateControllerState() { - final AndroidHostInterface hi = AndroidHostInterface.getInstance(); - if (mButtonCode >= 0) - hi.setControllerButtonState(mControllerIndex, mButtonCode, mPressed); - if (mAutoFireSlot >= 0) - hi.setControllerAutoFireState(mControllerIndex, mAutoFireSlot, mPressed); - - switch (mHotkey) - { - case FAST_FORWARD: { - hi.setFastForwardEnabled(mPressed); - } - break; - - case ANALOG_TOGGLE: { - if (!mPressed) - hi.toggleControllerAnalogMode(); - } - break; - - case OPEN_PAUSE_MENU: { - if (!mPressed) - hi.getEmulationActivity().openPauseMenu(); - } - break; - - case QUICK_LOAD: { - if (!mPressed) - hi.loadState(false, 0); - } - break; - - case QUICK_SAVE: { - if (!mPressed) - hi.saveState(false, 0); - } - break; - - case NONE: - default: - break; - } - } - - public Drawable getPressedDrawable() { - return mPressedDrawable; - } - - public void setPressedDrawable(Drawable pressedDrawable) { - mPressedDrawable = pressedDrawable; - } - - public Drawable getUnpressedDrawable() { - return mUnpressedDrawable; - } - - public void setUnpressedDrawable(Drawable unpressedDrawable) { - mUnpressedDrawable = unpressedDrawable; - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java deleted file mode 100644 index 797abb267..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerDPadView.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.View; - -public final class TouchscreenControllerDPadView extends View { - private static final int NUM_DIRECTIONS = 4; - private static final int NUM_POSITIONS = 8; - private static final int DIRECTION_UP = 0; - private static final int DIRECTION_RIGHT = 1; - private static final int DIRECTION_DOWN = 2; - private static final int DIRECTION_LEFT = 3; - - private final Drawable[] mUnpressedDrawables = new Drawable[NUM_DIRECTIONS]; - private final Drawable[] mPressedDrawables = new Drawable[NUM_DIRECTIONS]; - private final int[] mDirectionCodes = new int[] { -1, -1, -1, -1 }; - private final boolean[] mDirectionStates = new boolean[NUM_DIRECTIONS]; - - private boolean mPressed = false; - private int mPointerId = 0; - private int mPointerX = 0; - private int mPointerY = 0; - - private String mConfigName; - private boolean mDefaultVisibility = true; - - private int mControllerIndex = -1; - - private static final int[][] DRAWABLES = { - {R.drawable.ic_controller_up_button,R.drawable.ic_controller_up_button_pressed}, - {R.drawable.ic_controller_right_button,R.drawable.ic_controller_right_button_pressed}, - {R.drawable.ic_controller_down_button,R.drawable.ic_controller_down_button_pressed}, - {R.drawable.ic_controller_left_button,R.drawable.ic_controller_left_button_pressed}, - }; - - - public TouchscreenControllerDPadView(Context context) { - super(context); - init(); - } - - public TouchscreenControllerDPadView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public TouchscreenControllerDPadView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - private void init() { - for (int i = 0; i < NUM_DIRECTIONS; i++) { - mUnpressedDrawables[i] = getContext().getDrawable(DRAWABLES[i][0]); - mPressedDrawables[i] = getContext().getDrawable(DRAWABLES[i][1]); - } - } - - public String getConfigName() { - return mConfigName; - } - public void setConfigName(String configName) { - mConfigName = configName; - } - - public boolean getDefaultVisibility() { return mDefaultVisibility; } - public void setDefaultVisibility(boolean visibility) { mDefaultVisibility = visibility; } - - public void setControllerButtons(int controllerIndex, int leftCode, int rightCode, int upCode, int downCode) { - mControllerIndex = controllerIndex; - mDirectionCodes[DIRECTION_LEFT] = leftCode; - mDirectionCodes[DIRECTION_RIGHT] = rightCode; - mDirectionCodes[DIRECTION_UP] = upCode; - mDirectionCodes[DIRECTION_DOWN] = downCode; - } - - public void setUnpressed() { - if (!mPressed && mPointerX == 0 && mPointerY == 0) - return; - - mPressed = false; - mPointerX = 0; - mPointerY = 0; - updateControllerState(); - invalidate(); - } - - public void setPressed(int pointerId, int pointerX, int pointerY) { - final int posX = (int)(pointerX / getScaleX()); - final int posY = (int)(pointerY / getScaleY()); - - boolean doUpdate = (pointerId != mPointerId || !mPressed || (posX != mPointerX || posY != mPointerY)); - mPointerId = pointerId; - mPointerX = posX; - mPointerY = posY; - mPressed = true; - - if (doUpdate) { - updateControllerState(); - invalidate(); - } - } - - private void updateControllerState() { - final int subX = mPointerX / (getWidth() / 3); - final int subY = mPointerY / (getHeight() / 3); - - mDirectionStates[DIRECTION_UP] = (mPressed && subY == 0); - mDirectionStates[DIRECTION_RIGHT] = (mPressed && subX == 2); - mDirectionStates[DIRECTION_DOWN] = (mPressed && subY == 2); - mDirectionStates[DIRECTION_LEFT] = (mPressed && subX == 0); - - AndroidHostInterface hostInterface = AndroidHostInterface.getInstance(); - for (int i = 0; i < NUM_DIRECTIONS; i++) { - if (mDirectionCodes[i] >= 0) - hostInterface.setControllerButtonState(mControllerIndex, mDirectionCodes[i], mDirectionStates[i]); - } - } - - private void drawDirection(int direction, int subX, int subY, Canvas canvas, int buttonWidth, int buttonHeight) { - int leftBounds = subX * buttonWidth; - int rightBounds = leftBounds + buttonWidth; - int topBounds = subY * buttonHeight; - int bottomBounds = topBounds + buttonHeight; - - if (mDirectionStates[direction]) { - final int expandSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 10.0f, getResources().getDisplayMetrics()); - leftBounds -= expandSize; - rightBounds += expandSize; - topBounds -= expandSize; - bottomBounds += expandSize; - } - - final Drawable drawable = mDirectionStates[direction] ? mPressedDrawables[direction] : mUnpressedDrawables[direction]; - drawable.setBounds(leftBounds, topBounds, rightBounds, bottomBounds); - drawable.draw(canvas); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - final int width = getWidth(); - final int height = getHeight(); - - // Divide it into thirds - draw between. - final int buttonWidth = width / 3; - final int buttonHeight = height / 3; - - drawDirection(DIRECTION_UP, 1, 0, canvas, buttonWidth, buttonHeight); - drawDirection(DIRECTION_RIGHT, 2, 1, canvas, buttonWidth, buttonHeight); - drawDirection(DIRECTION_DOWN, 1, 2, canvas, buttonWidth, buttonHeight); - drawDirection(DIRECTION_LEFT, 0, 1, canvas, buttonWidth, buttonHeight); - } - - public boolean isPressed() { - return mPressed; - } - - public boolean hasPointerId() { - return mPointerId >= 0; - } - - public int getPointerId() { - return mPointerId; - } - - public void setPointerId(int mPointerId) { - this.mPointerId = mPointerId; - } -} 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 deleted file mode 100644 index 137e0bc75..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java +++ /dev/null @@ -1,858 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.SeekBar; - -import androidx.appcompat.app.AlertDialog; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.preference.PreferenceManager; - -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; - -/** - * TODO: document your custom view class. - */ -public class TouchscreenControllerView extends FrameLayout { - public static final int DEFAULT_OPACITY = 100; - - public static final float MIN_VIEW_SCALE = 0.25f; - public static final float MAX_VIEW_SCALE = 10.0f; - - public enum EditMode { - NONE, - POSITION, - SCALE - } - - private int mControllerIndex; - private String mControllerType; - private String mViewType; - private View mMainView; - private ArrayList mButtonViews = new ArrayList<>(); - private ArrayList mAxisViews = new ArrayList<>(); - private TouchscreenControllerDPadView mDPadView = null; - private int mPointerButtonCode = -1; - private int mPointerPointerId = -1; - private boolean mHapticFeedback; - private String mLayoutOrientation; - private EditMode mEditMode = EditMode.NONE; - private View mMovingView = null; - private String mMovingName = null; - private float mMovingLastX = 0.0f; - private float mMovingLastY = 0.0f; - private float mMovingLastScale = 0.0f; - private ConstraintLayout mEditLayout = null; - private int mOpacity = 100; - private Map mGlidePairs = new HashMap<>(); - - public TouchscreenControllerView(Context context) { - super(context); - setFocusable(false); - setFocusableInTouchMode(false); - } - - public TouchscreenControllerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TouchscreenControllerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - private String getConfigKeyForXTranslation(String name) { - return String.format("TouchscreenController/%s/%s%sXTranslation", mViewType, name, mLayoutOrientation); - } - - private String getConfigKeyForYTranslation(String name) { - return String.format("TouchscreenController/%s/%s%sYTranslation", mViewType, name, mLayoutOrientation); - } - - private String getConfigKeyForScale(String name) { - return String.format("TouchscreenController/%s/%s%sScale", mViewType, name, mLayoutOrientation); - } - - private String getConfigKeyForVisibility(String name) { - return String.format("TouchscreenController/%s/%s%sVisible", mViewType, name, mLayoutOrientation); - } - - private void saveSettingsForButton(String name, View view) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putFloat(getConfigKeyForXTranslation(name), view.getTranslationX()); - editor.putFloat(getConfigKeyForYTranslation(name), view.getTranslationY()); - editor.putFloat(getConfigKeyForScale(name), view.getScaleX()); - editor.commit(); - } - - private void saveVisibilityForButton(String name, boolean visible) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(getConfigKeyForVisibility(name), visible); - editor.commit(); - } - - private void clearTranslationForAllButtons() { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - final SharedPreferences.Editor editor = prefs.edit(); - - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - editor.remove(getConfigKeyForXTranslation(buttonView.getConfigName())); - editor.remove(getConfigKeyForYTranslation(buttonView.getConfigName())); - editor.remove(getConfigKeyForScale(buttonView.getConfigName())); - buttonView.setTranslationX(0.0f); - buttonView.setTranslationY(0.0f); - buttonView.setScaleX(1.0f); - buttonView.setScaleY(1.0f); - } - - for (TouchscreenControllerAxisView axisView : mAxisViews) { - editor.remove(getConfigKeyForXTranslation(axisView.getConfigName())); - editor.remove(getConfigKeyForYTranslation(axisView.getConfigName())); - editor.remove(getConfigKeyForScale(axisView.getConfigName())); - axisView.setTranslationX(0.0f); - axisView.setTranslationY(0.0f); - axisView.setScaleX(1.0f); - axisView.setScaleY(1.0f); - } - - if (mDPadView != null) { - editor.remove(getConfigKeyForXTranslation(mDPadView.getConfigName())); - editor.remove(getConfigKeyForYTranslation(mDPadView.getConfigName())); - editor.remove(getConfigKeyForScale(mDPadView.getConfigName())); - mDPadView.setTranslationX(0.0f); - mDPadView.setTranslationY(0.0f); - mDPadView.setScaleX(1.0f); - mDPadView.setScaleY(1.0f); - } - - editor.commit(); - requestLayout(); - } - - private void reloadButtonSettings() { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - try { - buttonView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(buttonView.getConfigName()), 0.0f)); - buttonView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(buttonView.getConfigName()), 0.0f)); - buttonView.setScaleX(prefs.getFloat(getConfigKeyForScale(buttonView.getConfigName()), 1.0f)); - buttonView.setScaleY(prefs.getFloat(getConfigKeyForScale(buttonView.getConfigName()), 1.0f)); - //Log.i("TouchscreenController", String.format("Translation for %s %f %f", buttonView.getConfigName(), - // buttonView.getTranslationX(), buttonView.getTranslationY())); - - final boolean visible = prefs.getBoolean(getConfigKeyForVisibility(buttonView.getConfigName()), buttonView.getDefaultVisibility()); - buttonView.setVisibility(visible ? VISIBLE : INVISIBLE); - } catch (ClassCastException ex) { - - } - } - - for (TouchscreenControllerAxisView axisView : mAxisViews) { - try { - axisView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(axisView.getConfigName()), 0.0f)); - axisView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(axisView.getConfigName()), 0.0f)); - axisView.setScaleX(prefs.getFloat(getConfigKeyForScale(axisView.getConfigName()), 1.0f)); - axisView.setScaleY(prefs.getFloat(getConfigKeyForScale(axisView.getConfigName()), 1.0f)); - - final boolean visible = prefs.getBoolean(getConfigKeyForVisibility(axisView.getConfigName()), axisView.getDefaultVisibility()); - axisView.setVisibility(visible ? VISIBLE : INVISIBLE); - } catch (ClassCastException ex) { - - } - } - - if (mDPadView != null) { - try { - mDPadView.setTranslationX(prefs.getFloat(getConfigKeyForXTranslation(mDPadView.getConfigName()), 0.0f)); - mDPadView.setTranslationY(prefs.getFloat(getConfigKeyForYTranslation(mDPadView.getConfigName()), 0.0f)); - mDPadView.setScaleX(prefs.getFloat(getConfigKeyForScale(mDPadView.getConfigName()), 1.0f)); - mDPadView.setScaleY(prefs.getFloat(getConfigKeyForScale(mDPadView.getConfigName()), 1.0f)); - - final boolean visible = prefs.getBoolean(getConfigKeyForVisibility(mDPadView.getConfigName()), mDPadView.getDefaultVisibility()); - mDPadView.setVisibility(visible ? VISIBLE : INVISIBLE); - } catch (ClassCastException ex) { - - } - } - } - - private void setOpacity(int opacity) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putInt("TouchscreenController/Opacity", opacity); - editor.commit(); - - updateOpacity(); - } - - private void updateOpacity() { - mOpacity = PreferenceManager.getDefaultSharedPreferences(getContext()).getInt("TouchscreenController/Opacity", DEFAULT_OPACITY); - - float alpha = (float)mOpacity / 100.0f; - alpha = (alpha < 0.0f) ? 0.0f : ((alpha > 1.0f) ? 1.0f : alpha); - - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - buttonView.setAlpha(alpha); - } - for (TouchscreenControllerAxisView axisView : mAxisViews) { - axisView.setAlpha(alpha); - } - if (mDPadView != null) - mDPadView.setAlpha(alpha); - } - - private String getOrientationString() { - switch (getContext().getResources().getConfiguration().orientation) { - case Configuration.ORIENTATION_PORTRAIT: - return "Portrait"; - case Configuration.ORIENTATION_LANDSCAPE: - default: - return "Landscape"; - } - } - - /** - * Checks if the orientation of the layout has changed, and if so, reloads button translations. - */ - public void updateOrientation() { - String newOrientation = getOrientationString(); - if (mLayoutOrientation != null && mLayoutOrientation.equals(newOrientation)) - return; - - Log.i("TouchscreenController", "New orientation: " + newOrientation); - mLayoutOrientation = newOrientation; - reloadButtonSettings(); - requestLayout(); - } - - public void init(int controllerIndex, String controllerType, String viewType, boolean hapticFeedback, boolean gliding) { - mControllerIndex = controllerIndex; - mControllerType = controllerType; - mViewType = viewType; - mHapticFeedback = hapticFeedback; - mLayoutOrientation = getOrientationString(); - - if (mEditMode != EditMode.NONE) - endLayoutEditing(); - - mButtonViews.clear(); - mAxisViews.clear(); - removeAllViews(); - - LayoutInflater inflater = LayoutInflater.from(getContext()); - String pointerButtonName = null; - switch (viewType) { - case "digital": - mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_digital, this, true); - break; - - case "analog_stick": - mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_stick, this, true); - break; - - case "analog_sticks": - mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_sticks, this, true); - break; - - case "lightgun": - mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_lightgun, this, true); - pointerButtonName = "Trigger"; - break; - - case "none": - default: - mMainView = null; - break; - } - - if (mMainView == null) - return; - - mMainView.setOnTouchListener((view1, event) -> { - if (mEditMode != EditMode.NONE) - return handleEditingTouchEvent(event); - else - return handleTouchEvent(event); - }); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - - linkDPadToButtons(mMainView, R.id.controller_dpad, "DPad", "", true); - linkButton(mMainView, R.id.controller_button_l1, "L1Button", "L1", true, gliding); - linkButton(mMainView, R.id.controller_button_l2, "L2Button", "L2", true, gliding); - linkButton(mMainView, R.id.controller_button_select, "SelectButton", "Select", true, gliding); - linkButton(mMainView, R.id.controller_button_start, "StartButton", "Start", true, gliding); - linkButton(mMainView, R.id.controller_button_triangle, "TriangleButton", "Triangle", true, gliding); - linkButton(mMainView, R.id.controller_button_circle, "CircleButton", "Circle", true, gliding); - linkButton(mMainView, R.id.controller_button_cross, "CrossButton", "Cross", true, gliding); - linkButton(mMainView, R.id.controller_button_square, "SquareButton", "Square", true, gliding); - linkButton(mMainView, R.id.controller_button_r1, "R1Button", "R1", true, gliding); - linkButton(mMainView, R.id.controller_button_r2, "R2Button", "R2", true, gliding); - - if (!linkAxis(mMainView, R.id.controller_axis_left, "LeftAxis", "Left", true)) - linkAxisToButtons(mMainView, R.id.controller_axis_left, "LeftAxis", ""); - - linkAxis(mMainView, R.id.controller_axis_right, "RightAxis", "Right", true); - - // GunCon - linkButton(mMainView, R.id.controller_button_a, "AButton", "A", true, true); - linkButton(mMainView, R.id.controller_button_b, "BButton", "B", true, true); - if (pointerButtonName != null) - linkPointer(pointerButtonName); - - // Turbo/autofire buttons - linkAutoFireButton(mMainView, R.id.controller_button_autofire_1, "AutoFire1", 0, false); - linkAutoFireButton(mMainView, R.id.controller_button_autofire_2, "AutoFire2", 1, false); - linkAutoFireButton(mMainView, R.id.controller_button_autofire_3, "AutoFire3", 2, false); - linkAutoFireButton(mMainView, R.id.controller_button_autofire_4, "AutoFire4", 3, false); - - // Hotkeys - linkHotkeyButton(mMainView, R.id.controller_button_fast_forward, "FastForward", - TouchscreenControllerButtonView.Hotkey.FAST_FORWARD, false); - linkHotkeyButton(mMainView, R.id.controller_button_analog, "AnalogToggle", - TouchscreenControllerButtonView.Hotkey.ANALOG_TOGGLE, false); - linkHotkeyButton(mMainView, R.id.controller_button_pause, "OpenPauseMenu", - TouchscreenControllerButtonView.Hotkey.OPEN_PAUSE_MENU, true); - linkHotkeyButton(mMainView, R.id.controller_button_quick_load, "QuickLoad", - TouchscreenControllerButtonView.Hotkey.QUICK_LOAD, false); - linkHotkeyButton(mMainView, R.id.controller_button_quick_save, "QuickSave", - TouchscreenControllerButtonView.Hotkey.QUICK_SAVE, false); - - reloadButtonSettings(); - updateOpacity(); - requestLayout(); - } - - private void linkButton(View view, int id, String configName, String buttonName, boolean defaultVisibility, boolean isGlidable) { - TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); - if (buttonView == null) - return; - - buttonView.setConfigName(configName); - buttonView.setDefaultVisibility(defaultVisibility); - buttonView.setIsGlidable(isGlidable); - mButtonViews.add(buttonView); - - int code = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonName); - Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code)); - - if (code >= 0) { - buttonView.setButtonCode(mControllerIndex, code); - buttonView.setHapticFeedback(mHapticFeedback); - } else { - Log.e("TouchscreenController", String.format("Unknown button name '%s' " + - "for '%s'", buttonName, mControllerType)); - } - } - - private boolean linkAxis(View view, int id, String configName, String axisName, boolean defaultVisibility) { - TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id); - if (axisView == null) - return false; - - axisView.setConfigName(configName); - axisView.setDefaultVisibility(defaultVisibility); - mAxisViews.add(axisView); - - int xCode = AndroidHostInterface.getControllerAxisCode(mControllerType, axisName + "X"); - int yCode = AndroidHostInterface.getControllerAxisCode(mControllerType, axisName + "Y"); - Log.i("TouchscreenController", String.format("%s -> %d/%d", axisName, xCode, yCode)); - if (xCode < 0 && yCode < 0) - return false; - - axisView.setControllerAxis(mControllerIndex, xCode, yCode); - return true; - } - - private boolean linkAxisToButtons(View view, int id, String configName, String buttonPrefix) { - TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id); - if (axisView == null) - return false; - - int leftCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Left"); - int rightCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Right"); - int upCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Up"); - int downCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Down"); - Log.i("TouchscreenController", String.format("%s(ButtonAxis) -> %d,%d,%d,%d", buttonPrefix, leftCode, rightCode, upCode, downCode)); - if (leftCode < 0 && rightCode < 0 && upCode < 0 && downCode < 0) - return false; - - axisView.setControllerButtons(mControllerIndex, leftCode, rightCode, upCode, downCode); - return true; - } - - private boolean linkDPadToButtons(View view, int id, String configName, String buttonPrefix, boolean defaultVisibility) { - TouchscreenControllerDPadView dpadView = (TouchscreenControllerDPadView) view.findViewById(id); - if (dpadView == null) - return false; - - dpadView.setConfigName(configName); - dpadView.setDefaultVisibility(defaultVisibility); - mDPadView = dpadView; - - int leftCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Left"); - int rightCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Right"); - int upCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Up"); - int downCode = AndroidHostInterface.getControllerButtonCode(mControllerType, buttonPrefix + "Down"); - Log.i("TouchscreenController", String.format("%s(DPad) -> %d,%d,%d,%d", buttonPrefix, leftCode, rightCode, upCode, downCode)); - if (leftCode < 0 && rightCode < 0 && upCode < 0 && downCode < 0) - return false; - - dpadView.setControllerButtons(mControllerIndex, leftCode, rightCode, upCode, downCode); - return true; - } - - private void linkHotkeyButton(View view, int id, String configName, TouchscreenControllerButtonView.Hotkey hotkey, boolean defaultVisibility) { - TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); - if (buttonView == null) - return; - - buttonView.setConfigName(configName); - buttonView.setDefaultVisibility(defaultVisibility); - buttonView.setHotkey(hotkey); - buttonView.setIsGlidable(false); - mButtonViews.add(buttonView); - } - - private void linkAutoFireButton(View view, int id, String configName, int slot, boolean defaultVisibility) { - TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); - if (buttonView == null) - return; - - buttonView.setConfigName(configName); - buttonView.setDefaultVisibility(defaultVisibility); - buttonView.setAutoFireSlot(mControllerIndex, slot); - buttonView.setIsGlidable(true); - mButtonViews.add(buttonView); - } - - private boolean linkPointer(String buttonName) { - mPointerButtonCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName); - Log.i("TouchscreenController", String.format("Pointer -> %s,%d", buttonName, mPointerButtonCode)); - return (mPointerButtonCode >= 0); - } - - private int dpToPixels(float dp) { - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics())); - } - - public void startLayoutEditing(EditMode mode) { - if (mEditLayout == null) { - LayoutInflater inflater = LayoutInflater.from(getContext()); - mEditLayout = (ConstraintLayout) inflater.inflate(R.layout.layout_touchscreen_controller_edit, this, false); - ((Button) mEditLayout.findViewById(R.id.options)).setOnClickListener((view) -> showEditorMenu()); - addView(mEditLayout); - } - - mEditMode = mode; - } - - public void endLayoutEditing() { - if (mEditLayout != null) { - ((ViewGroup) mMainView).removeView(mEditLayout); - mEditLayout = null; - } - - mEditMode = EditMode.NONE; - mMovingView = null; - mMovingName = null; - mMovingLastX = 0.0f; - mMovingLastY = 0.0f; - - // unpause if we're paused (from the setting) - if (AndroidHostInterface.getInstance().isEmulationThreadPaused()) - AndroidHostInterface.getInstance().pauseEmulationThread(false); - } - - private float snapToValue(float pos, float value) { - return Math.round(pos / value) * value; - } - - private float snapToGrid(float pos) { - final float value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20.0f, getResources().getDisplayMetrics()); - return snapToValue(pos, value); - } - - private boolean handleEditingTouchEvent(MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_UP: { - if (mMovingView != null) { - // save position - saveSettingsForButton(mMovingName, mMovingView); - mMovingView = null; - mMovingName = null; - mMovingLastX = 0.0f; - mMovingLastY = 0.0f; - mMovingLastScale = 0.0f; - } - - return true; - } - - case MotionEvent.ACTION_DOWN: { - if (mMovingView != null) { - // already moving a button - return true; - } - - Rect rect = new Rect(); - final float x = event.getX(); - final float y = event.getY(); - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - buttonView.getHitRect(rect); - if (rect.contains((int) x, (int) y)) { - mMovingView = buttonView; - mMovingName = buttonView.getConfigName(); - mMovingLastX = snapToGrid(x); - mMovingLastY = snapToGrid(y); - mMovingLastScale = buttonView.getScaleX(); - return true; - } - } - - for (TouchscreenControllerAxisView axisView : mAxisViews) { - axisView.getHitRect(rect); - if (rect.contains((int) x, (int) y)) { - mMovingView = axisView; - mMovingName = axisView.getConfigName(); - mMovingLastX = snapToGrid(x); - mMovingLastY = snapToGrid(y); - mMovingLastScale = axisView.getScaleX(); - return true; - } - } - - if (mDPadView != null) { - mDPadView.getHitRect(rect); - if (rect.contains((int) x, (int) y)) { - mMovingView = mDPadView; - mMovingName = mDPadView.getConfigName(); - mMovingLastX = snapToGrid(x); - mMovingLastY = snapToGrid(y); - mMovingLastScale = mDPadView.getScaleX(); - return true; - } - } - - // nothing.. - return true; - } - - case MotionEvent.ACTION_MOVE: { - if (mMovingView == null) - return true; - - final float x = snapToGrid(event.getX()); - final float y = snapToGrid(event.getY()); - if (mEditMode == EditMode.POSITION) { - final float dx = x - mMovingLastX; - final float dy = y - mMovingLastY; - mMovingLastX = x; - mMovingLastY = y; - - final float posX = mMovingView.getX() + dx; - final float posY = mMovingView.getY() + dy; - //Log.d("Position", String.format("%f %f -> (%f %f) %f %f", - // mMovingView.getX(), mMovingView.getY(), dx, dy, posX, posY)); - mMovingView.setX(posX); - mMovingView.setY(posY); - } else { - final float lastDx = mMovingLastX - mMovingView.getX(); - final float lastDy = mMovingLastY - mMovingView.getY(); - final float dx = x - mMovingView.getX(); - final float dy = y - mMovingView.getY(); - final float lastDistance = Math.max(Math.abs(lastDx), Math.abs(lastDy)); - final float distance = Math.max(Math.abs(dx), Math.abs(dy)); - final float scaler = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50.0f, getResources().getDisplayMetrics()); - final float scaleDiff = snapToValue((distance - lastDistance) / scaler, 0.1f); - final float scale = Math.max(Math.min(mMovingLastScale + mMovingLastScale * scaleDiff, MAX_VIEW_SCALE), MIN_VIEW_SCALE); - mMovingView.setScaleX(scale); - mMovingView.setScaleY(scale); - } - - mMovingView.invalidate(); - mMainView.requestLayout(); - return true; - } - } - - return false; - } - - private boolean updateTouchButtonsFromEvent(MotionEvent event) { - if (!AndroidHostInterface.hasInstanceAndEmulationThreadIsRunning()) - return false; - - Rect rect = new Rect(); - final int actionMasked = event.getActionMasked(); - final int pointerCount = event.getPointerCount(); - final int liftedPointerIndex = (actionMasked == MotionEvent.ACTION_POINTER_UP) ? event.getActionIndex() : -1; - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - if (buttonView.getVisibility() != VISIBLE) - continue; - - buttonView.getHitRect(rect); - boolean pressed = false; - for (int i = 0; i < pointerCount; i++) { - if (i == liftedPointerIndex) - continue; - - final int x = (int) event.getX(i); - final int y = (int) event.getY(i); - if (rect.contains(x, y)) { - buttonView.setPressed(true); - final int pointerId = event.getPointerId(i); - if (!mGlidePairs.containsKey(pointerId) && !mGlidePairs.containsValue(buttonView)) { - if (buttonView.getIsGlidable()) - mGlidePairs.put(pointerId, buttonView); - else { mGlidePairs.put(pointerId, null); } - } - pressed = true; - break; - } - } - - if (!pressed && !mGlidePairs.containsValue(buttonView)) - buttonView.setPressed(pressed); - } - - for (TouchscreenControllerAxisView axisView : mAxisViews) { - if (axisView.getVisibility() != VISIBLE) - continue; - - axisView.getHitRect(rect); - boolean pressed = false; - for (int i = 0; i < pointerCount; i++) { - if (i == liftedPointerIndex) - continue; - - final int pointerId = event.getPointerId(i); - final int x = (int) event.getX(i); - final int y = (int) event.getY(i); - - if ((rect.contains(x, y) && !axisView.isPressed()) || - (axisView.isPressed() && axisView.getPointerId() == pointerId)) { - axisView.setPressed(pointerId, x - rect.left, y - rect.top); - pressed = true; - mGlidePairs.put(pointerId, null); - break; - } - } - if (!pressed) - axisView.setUnpressed(); - } - - if (mDPadView != null && mDPadView.getVisibility() == VISIBLE) { - mDPadView.getHitRect(rect); - - boolean pressed = false; - for (int i = 0; i < pointerCount; i++) { - if (i == liftedPointerIndex) - continue; - - final int x = (int) event.getX(i); - final int y = (int) event.getY(i); - if (rect.contains(x, y)) { - mDPadView.setPressed(event.getPointerId(i), x - rect.left, y - rect.top); - pressed = true; - } - } - - if (!pressed) - mDPadView.setUnpressed(); - } - - if (mPointerButtonCode >= 0) { - final int pointerIndex = event.getActionIndex(); - final int pointerId = event.getPointerId(pointerIndex); - if (mPointerPointerId < 0 && (actionMasked == MotionEvent.ACTION_DOWN || actionMasked == MotionEvent.ACTION_POINTER_DOWN)) { - if (!mGlidePairs.containsKey(pointerId)) { - AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, - mPointerButtonCode, true); - mPointerPointerId = pointerId; - } - } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) { - if (pointerId == mPointerPointerId) { - AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, - mPointerButtonCode, false); - mPointerPointerId = -1; - } - } - - AndroidHostInterface.getInstance().setMousePosition( - (int) event.getX(pointerIndex), - (int) event.getY(pointerIndex)); - } - - return true; - } - - private boolean handleTouchEvent(MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_UP: { - if (!AndroidHostInterface.hasInstanceAndEmulationThreadIsRunning()) - return false; - - mGlidePairs.clear(); - - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - buttonView.setPressed(false); - } - - for (TouchscreenControllerAxisView axisView : mAxisViews) { - axisView.setUnpressed(); - } - - if (mDPadView != null) - mDPadView.setUnpressed(); - - if (mPointerPointerId >= 0) { - AndroidHostInterface.getInstance().setControllerButtonState( - mControllerIndex, mPointerButtonCode, false); - mPointerPointerId = -1; - } - - return true; - } - - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = event.getPointerId(event.getActionIndex()); - if (mGlidePairs.containsKey(pointerId)) - mGlidePairs.remove(pointerId); - - return updateTouchButtonsFromEvent(event); - } - case MotionEvent.ACTION_MOVE: { - return updateTouchButtonsFromEvent(event); - } - } - - return false; - } - - public AlertDialog.Builder createAddRemoveButtonDialog(Context context) { - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final CharSequence[] items = new CharSequence[mButtonViews.size() + mAxisViews.size()]; - final boolean[] itemsChecked = new boolean[mButtonViews.size() + mAxisViews.size()]; - int itemCount = 0; - for (TouchscreenControllerButtonView buttonView : mButtonViews) { - items[itemCount] = buttonView.getConfigName(); - itemsChecked[itemCount] = buttonView.getVisibility() == VISIBLE; - itemCount++; - } - for (TouchscreenControllerAxisView axisView : mAxisViews) { - items[itemCount] = axisView.getConfigName(); - itemsChecked[itemCount] = axisView.getVisibility() == VISIBLE; - itemCount++; - } - - builder.setTitle(R.string.dialog_touchscreen_controller_buttons); - builder.setMultiChoiceItems(items, itemsChecked, (dialog, which, isChecked) -> { - if (which < mButtonViews.size()) { - TouchscreenControllerButtonView buttonView = mButtonViews.get(which); - buttonView.setVisibility(isChecked ? VISIBLE : INVISIBLE); - saveVisibilityForButton(buttonView.getConfigName(), isChecked); - } else { - TouchscreenControllerAxisView axisView = mAxisViews.get(which - mButtonViews.size()); - axisView.setVisibility(isChecked ? VISIBLE : INVISIBLE); - saveVisibilityForButton(axisView.getConfigName(), isChecked); - } - }); - builder.setNegativeButton(R.string.dialog_done, (dialog, which) -> { - dialog.dismiss(); - }); - - return builder; - } - - public AlertDialog.Builder createOpacityDialog(Context context) { - final SeekBar seekBar = new SeekBar(context); - seekBar.setMax(100); - seekBar.setProgress(mOpacity); - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - setOpacity(progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.dialog_touchscreen_controller_opacity); - builder.setView(seekBar); - builder.setNegativeButton(R.string.dialog_done, (dialog, which) -> { - dialog.dismiss(); - }); - return builder; - } - - private void showEditorMenu() { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setItems(R.array.touchscreen_layout_menu, (dialogInterface, i) -> { - switch (i) { - case 0: // Change Opacity - { - AlertDialog.Builder subBuilder = createOpacityDialog(getContext()); - subBuilder.create().show(); - } - break; - - case 1: // Add/Remove Buttons - { - AlertDialog.Builder subBuilder = createAddRemoveButtonDialog(getContext()); - subBuilder.create().show(); - } - break; - - case 2: // Edit Positions - { - mEditMode = EditMode.POSITION; - } - break; - - case 3: // Edit Scale - { - mEditMode = EditMode.SCALE; - } - break; - - case 4: // Reset Layout - { - clearTranslationForAllButtons(); - } - break; - - case 5: // Exit Editor - { - endLayoutEditing(); - } - break; - } - }); - builder.create().show(); - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java b/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java deleted file mode 100644 index 6da425237..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/URLDownloader.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.github.stenzek.duckstation; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * Helper class for exposing HTTP downloads to native code without pulling in an external - * dependency for doing so. - */ -public class URLDownloader { - private int statusCode = -1; - private byte[] data = null; - private final String userAgent; - - public URLDownloader(String userAgent) { - this.userAgent = userAgent; - } - - private HttpURLConnection getConnection(String url) { - try { - final URL parsedUrl = new URL(url); - HttpURLConnection connection = (HttpURLConnection) parsedUrl.openConnection(); - if (connection == null) - throw new RuntimeException(String.format("openConnection(%s) returned null", url)); - - if (userAgent != null) - connection.addRequestProperty("User-Agent", userAgent); - return connection; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public int getStatusCode() { - return statusCode; - } - - public byte[] getData() { - return data; - } - - private boolean download(HttpURLConnection connection) { - try { - statusCode = connection.getResponseCode(); - if (statusCode != HttpURLConnection.HTTP_OK) - return false; - - final InputStream inStream = new BufferedInputStream(connection.getInputStream()); - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - final int CHUNK_SIZE = 128 * 1024; - final byte[] chunk = new byte[CHUNK_SIZE]; - int len; - while ((len = inStream.read(chunk)) > 0) { - outputStream.write(chunk, 0, len); - } - - data = outputStream.toByteArray(); - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - public boolean get(String url) { - final HttpURLConnection connection = getConnection(url); - if (connection == null) - return false; - - return download(connection); - } - - public boolean post(String url, byte[] postData) { - final HttpURLConnection connection = getConnection(url); - if (connection == null) - return false; - - try { - connection.setDoOutput(true); - connection.setChunkedStreamingMode(0); - - OutputStream postStream = new BufferedOutputStream(connection.getOutputStream()); - postStream.write(postData); - return download(connection); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } -} diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/UpdateNotes.java b/android/app/src/main/java/com/github/stenzek/duckstation/UpdateNotes.java deleted file mode 100644 index 6ef63878f..000000000 --- a/android/app/src/main/java/com/github/stenzek/duckstation/UpdateNotes.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.github.stenzek.duckstation; - -import android.app.AlertDialog; -import android.content.Intent; -import android.content.SharedPreferences; - -import androidx.preference.PreferenceManager; - -public class UpdateNotes { - private static final int VERSION_CONTROLLER_UPDATE = 1; - private static final int CURRENT_VERSION = VERSION_CONTROLLER_UPDATE; - - private static final String CONFIG_KEY = "Main/UpdateNotesVersion"; - - private static int getVersion(MainActivity parent) { - try { - final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(parent); - return sp.getInt(CONFIG_KEY, 0); - } catch (Exception e) { - e.printStackTrace(); - return CURRENT_VERSION; - } - } - - public static void setVersion(MainActivity parent, int version) { - final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(parent); - sp.edit().putInt(CONFIG_KEY, version).commit(); - } - - public static boolean displayUpdateNotes(MainActivity parent) { - final int version = getVersion(parent); - - if (version < VERSION_CONTROLLER_UPDATE ) { - displayControllerUpdateNotes(parent); - setVersion(parent, VERSION_CONTROLLER_UPDATE); - return true; - } - - return false; - } - - public static void displayControllerUpdateNotes(MainActivity parent) { - final AlertDialog.Builder builder = new AlertDialog.Builder(parent); - builder.setTitle(R.string.update_notes_title); - builder.setMessage(R.string.update_notes_message_version_controller_update); - builder.setPositiveButton(R.string.main_activity_yes, (dialog, which) -> { - dialog.dismiss(); - Intent intent = new Intent(parent, ControllerSettingsActivity.class); - parent.startActivity(intent); - }); - builder.setNegativeButton(R.string.main_activity_no, (dialog, which) -> dialog.dismiss()); - builder.create().show(); - } -} diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 1f6bb2906..000000000 --- a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/cover_placeholder.png b/android/app/src/main/res/drawable/cover_placeholder.png deleted file mode 100644 index 18dc446fe..000000000 Binary files a/android/app/src/main/res/drawable/cover_placeholder.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/duck.png b/android/app/src/main/res/drawable/duck.png deleted file mode 100644 index f039412f2..000000000 Binary files a/android/app/src/main/res/drawable/duck.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/flag_eu.xml b/android/app/src/main/res/drawable/flag_eu.xml deleted file mode 100644 index e545a72a2..000000000 --- a/android/app/src/main/res/drawable/flag_eu.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/flag_jp.xml b/android/app/src/main/res/drawable/flag_jp.xml deleted file mode 100644 index f7b9fe6bd..000000000 --- a/android/app/src/main/res/drawable/flag_jp.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/flag_us.xml b/android/app/src/main/res/drawable/flag_us.xml deleted file mode 100644 index d83fcb9e2..000000000 --- a/android/app/src/main/res/drawable/flag_us.xml +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_baseline_album_24.xml b/android/app/src/main/res/drawable/ic_baseline_album_24.xml deleted file mode 100644 index 58c0ba697..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_album_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/android/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml deleted file mode 100644 index 2a31b2ef3..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_category_24.xml b/android/app/src/main/res/drawable/ic_baseline_category_24.xml deleted file mode 100644 index dead295df..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_category_24.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_close_24.xml b/android/app/src/main/res/drawable/ic_baseline_close_24.xml deleted file mode 100644 index 16d6d37dd..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_close_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_create_new_folder_24.xml b/android/app/src/main/res/drawable/ic_baseline_create_new_folder_24.xml deleted file mode 100644 index 78c162283..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_create_new_folder_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_delete_24.xml b/android/app/src/main/res/drawable/ic_baseline_delete_24.xml deleted file mode 100644 index 3c4030b03..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_delete_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_delete_sweep_24.xml b/android/app/src/main/res/drawable/ic_baseline_delete_sweep_24.xml deleted file mode 100644 index 22560a4f9..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_delete_sweep_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_exit_to_app_24.xml b/android/app/src/main/res/drawable/ic_baseline_exit_to_app_24.xml deleted file mode 100644 index ed42b700b..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_exit_to_app_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml b/android/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml deleted file mode 100644 index e3f30c6c7..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_fast_rewind_24.xml b/android/app/src/main/res/drawable/ic_baseline_fast_rewind_24.xml deleted file mode 100644 index 81f79b322..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_fast_rewind_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_folder_24.xml b/android/app/src/main/res/drawable/ic_baseline_folder_24.xml deleted file mode 100644 index bbfe9c931..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_folder_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml b/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml deleted file mode 100644 index f58b501e3..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_gamepad_24.xml b/android/app/src/main/res/drawable/ic_baseline_gamepad_24.xml deleted file mode 100644 index f4a8a8711..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_gamepad_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_grid_view_24.xml b/android/app/src/main/res/drawable/ic_baseline_grid_view_24.xml deleted file mode 100644 index 6d4f4a563..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_grid_view_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_help_24.xml b/android/app/src/main/res/drawable/ic_baseline_help_24.xml deleted file mode 100644 index c0c92681d..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_help_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml b/android/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml deleted file mode 100644 index 99a23c4db..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml b/android/app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml deleted file mode 100644 index 18b306228..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_library_music_24.xml b/android/app/src/main/res/drawable/ic_baseline_library_music_24.xml deleted file mode 100644 index 2ba294bd7..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_library_music_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_lock_24.xml b/android/app/src/main/res/drawable/ic_baseline_lock_24.xml deleted file mode 100644 index d6191026a..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_lock_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_lock_open_24.xml b/android/app/src/main/res/drawable/ic_baseline_lock_open_24.xml deleted file mode 100644 index a11b70e62..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_lock_open_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_menu_24.xml b/android/app/src/main/res/drawable/ic_baseline_menu_24.xml deleted file mode 100644 index 4350ba96a..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_menu_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_not_interested_60.xml b/android/app/src/main/res/drawable/ic_baseline_not_interested_60.xml deleted file mode 100644 index f5f7da3ae..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_not_interested_60.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml deleted file mode 100644 index 41f4a52ed..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_playlist_play_24.xml b/android/app/src/main/res/drawable/ic_baseline_playlist_play_24.xml deleted file mode 100644 index 67f9b5735..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_playlist_play_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml b/android/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml deleted file mode 100644 index 05a669ab2..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_radio_button_unchecked_24.xml b/android/app/src/main/res/drawable/ic_baseline_radio_button_unchecked_24.xml deleted file mode 100644 index 2d5445cbe..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_radio_button_unchecked_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_restart_alt_24.xml b/android/app/src/main/res/drawable/ic_baseline_restart_alt_24.xml deleted file mode 100644 index 2f9f24ca9..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_restart_alt_24.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_save_24.xml b/android/app/src/main/res/drawable/ic_baseline_save_24.xml deleted file mode 100644 index 955858d7d..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_save_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_sd_card_24.xml b/android/app/src/main/res/drawable/ic_baseline_sd_card_24.xml deleted file mode 100644 index 84f6ea6ac..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_sd_card_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_settings_24.xml b/android/app/src/main/res/drawable/ic_baseline_settings_24.xml deleted file mode 100644 index d99a8ae40..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_settings_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_tips_and_updates_24.xml b/android/app/src/main/res/drawable/ic_baseline_tips_and_updates_24.xml deleted file mode 100644 index 93d588228..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_tips_and_updates_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_touch_app_24.xml b/android/app/src/main/res/drawable/ic_baseline_touch_app_24.xml deleted file mode 100644 index 27f451658..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_touch_app_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_trophy_24.xml b/android/app/src/main/res/drawable/ic_baseline_trophy_24.xml deleted file mode 100644 index dbf6358f8..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_trophy_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_vibration_24.xml b/android/app/src/main/res/drawable/ic_baseline_vibration_24.xml deleted file mode 100644 index a9600b74e..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_vibration_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_view_list_24.xml b/android/app/src/main/res/drawable/ic_baseline_view_list_24.xml deleted file mode 100644 index 9884aeb83..000000000 --- a/android/app/src/main/res/drawable/ic_baseline_view_list_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_a_button.xml b/android/app/src/main/res/drawable/ic_controller_a_button.xml deleted file mode 100644 index 74124b194..000000000 --- a/android/app/src/main/res/drawable/ic_controller_a_button.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_a_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_a_button_pressed.xml deleted file mode 100644 index 302d83cce..000000000 --- a/android/app/src/main/res/drawable/ic_controller_a_button_pressed.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_analog_base.xml b/android/app/src/main/res/drawable/ic_controller_analog_base.xml deleted file mode 100644 index 85168053b..000000000 --- a/android/app/src/main/res/drawable/ic_controller_analog_base.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_analog_button.xml b/android/app/src/main/res/drawable/ic_controller_analog_button.xml deleted file mode 100644 index fec0a0a6e..000000000 --- a/android/app/src/main/res/drawable/ic_controller_analog_button.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_analog_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_analog_button_pressed.xml deleted file mode 100644 index b08bd5e16..000000000 --- a/android/app/src/main/res/drawable/ic_controller_analog_button_pressed.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml b/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml deleted file mode 100644 index 7304ff569..000000000 --- a/android/app/src/main/res/drawable/ic_controller_analog_stick_pressed.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml b/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml deleted file mode 100644 index 121c376ba..000000000 --- a/android/app/src/main/res/drawable/ic_controller_analog_stick_unpressed.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_b_button.xml b/android/app/src/main/res/drawable/ic_controller_b_button.xml deleted file mode 100644 index 8f25810d9..000000000 --- a/android/app/src/main/res/drawable/ic_controller_b_button.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_b_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_b_button_pressed.xml deleted file mode 100644 index 353d7e5f2..000000000 --- a/android/app/src/main/res/drawable/ic_controller_b_button_pressed.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button.xml b/android/app/src/main/res/drawable/ic_controller_circle_button.xml deleted file mode 100644 index d9fa460c6..000000000 --- a/android/app/src/main/res/drawable/ic_controller_circle_button.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml deleted file mode 100644 index 47953d0c1..000000000 --- a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button.xml b/android/app/src/main/res/drawable/ic_controller_cross_button.xml deleted file mode 100644 index b133bd616..000000000 --- a/android/app/src/main/res/drawable/ic_controller_cross_button.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml deleted file mode 100644 index b00988473..000000000 --- a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_down_button.xml b/android/app/src/main/res/drawable/ic_controller_down_button.xml deleted file mode 100644 index 898f2c54f..000000000 --- a/android/app/src/main/res/drawable/ic_controller_down_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml deleted file mode 100644 index 4e7d5a35e..000000000 --- a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_fast_forward.xml b/android/app/src/main/res/drawable/ic_controller_fast_forward.xml deleted file mode 100644 index 37e89e29b..000000000 --- a/android/app/src/main/res/drawable/ic_controller_fast_forward.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_fast_forward_pressed.xml b/android/app/src/main/res/drawable/ic_controller_fast_forward_pressed.xml deleted file mode 100644 index 2a505aad8..000000000 --- a/android/app/src/main/res/drawable/ic_controller_fast_forward_pressed.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button.xml b/android/app/src/main/res/drawable/ic_controller_l1_button.xml deleted file mode 100644 index 9f3ab7240..000000000 --- a/android/app/src/main/res/drawable/ic_controller_l1_button.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml deleted file mode 100644 index 06d625fe5..000000000 --- a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button.xml b/android/app/src/main/res/drawable/ic_controller_l2_button.xml deleted file mode 100644 index 3853d103f..000000000 --- a/android/app/src/main/res/drawable/ic_controller_l2_button.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml deleted file mode 100644 index eeaefb18b..000000000 --- a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_left_button.xml b/android/app/src/main/res/drawable/ic_controller_left_button.xml deleted file mode 100644 index ccd46c3cc..000000000 --- a/android/app/src/main/res/drawable/ic_controller_left_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml deleted file mode 100644 index 39c1de9e1..000000000 --- a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_pause_button.xml b/android/app/src/main/res/drawable/ic_controller_pause_button.xml deleted file mode 100644 index 9b64f42b0..000000000 --- a/android/app/src/main/res/drawable/ic_controller_pause_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_quick_load_button.xml b/android/app/src/main/res/drawable/ic_controller_quick_load_button.xml deleted file mode 100644 index f4197876c..000000000 --- a/android/app/src/main/res/drawable/ic_controller_quick_load_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_quick_save_button.xml b/android/app/src/main/res/drawable/ic_controller_quick_save_button.xml deleted file mode 100644 index 2a81bac25..000000000 --- a/android/app/src/main/res/drawable/ic_controller_quick_save_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button.xml b/android/app/src/main/res/drawable/ic_controller_r1_button.xml deleted file mode 100644 index 3130def38..000000000 --- a/android/app/src/main/res/drawable/ic_controller_r1_button.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml deleted file mode 100644 index 352ddbc6a..000000000 --- a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button.xml b/android/app/src/main/res/drawable/ic_controller_r2_button.xml deleted file mode 100644 index 195fbe85d..000000000 --- a/android/app/src/main/res/drawable/ic_controller_r2_button.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml deleted file mode 100644 index 640c36863..000000000 --- a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_right_button.xml b/android/app/src/main/res/drawable/ic_controller_right_button.xml deleted file mode 100644 index 8545a61ce..000000000 --- a/android/app/src/main/res/drawable/ic_controller_right_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml deleted file mode 100644 index f0cff05ba..000000000 --- a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_select_button.xml b/android/app/src/main/res/drawable/ic_controller_select_button.xml deleted file mode 100644 index bea56389c..000000000 --- a/android/app/src/main/res/drawable/ic_controller_select_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml deleted file mode 100644 index a34a925d4..000000000 --- a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_square_button.xml b/android/app/src/main/res/drawable/ic_controller_square_button.xml deleted file mode 100644 index 0da658c01..000000000 --- a/android/app/src/main/res/drawable/ic_controller_square_button.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml deleted file mode 100644 index 248a1d400..000000000 --- a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_start_button.xml b/android/app/src/main/res/drawable/ic_controller_start_button.xml deleted file mode 100644 index 7247bbcd7..000000000 --- a/android/app/src/main/res/drawable/ic_controller_start_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml deleted file mode 100644 index 5a8255577..000000000 --- a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t1_button.xml b/android/app/src/main/res/drawable/ic_controller_t1_button.xml deleted file mode 100644 index 2f342f01c..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t1_button.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml deleted file mode 100644 index bf3ffaf35..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t1_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t2_button.xml b/android/app/src/main/res/drawable/ic_controller_t2_button.xml deleted file mode 100644 index abddf7071..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t2_button.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml deleted file mode 100644 index 2b07da75f..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t2_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t3_button.xml b/android/app/src/main/res/drawable/ic_controller_t3_button.xml deleted file mode 100644 index d9b001f00..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t3_button.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml deleted file mode 100644 index 78465f7fd..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t3_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t4_button.xml b/android/app/src/main/res/drawable/ic_controller_t4_button.xml deleted file mode 100644 index 87f5e6726..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t4_button.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml deleted file mode 100644 index b7da1f1b2..000000000 --- a/android/app/src/main/res/drawable/ic_controller_t4_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button.xml deleted file mode 100644 index 9a1392988..000000000 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml deleted file mode 100644 index be10c7a85..000000000 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/android/app/src/main/res/drawable/ic_controller_up_button.xml b/android/app/src/main/res/drawable/ic_controller_up_button.xml deleted file mode 100644 index e8b5a4064..000000000 --- a/android/app/src/main/res/drawable/ic_controller_up_button.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml deleted file mode 100644 index d17013321..000000000 --- a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/android/app/src/main/res/drawable/ic_emblem_system.xml b/android/app/src/main/res/drawable/ic_emblem_system.xml deleted file mode 100644 index 4be483cfc..000000000 --- a/android/app/src/main/res/drawable/ic_emblem_system.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 0d025f9bf..000000000 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/ic_media_cdrom.xml b/android/app/src/main/res/drawable/ic_media_cdrom.xml deleted file mode 100644 index 9269ea16f..000000000 --- a/android/app/src/main/res/drawable/ic_media_cdrom.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/ic_star_0.png b/android/app/src/main/res/drawable/ic_star_0.png deleted file mode 100644 index e5b56db70..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_0.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_star_1.png b/android/app/src/main/res/drawable/ic_star_1.png deleted file mode 100644 index ae91a29ac..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_1.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_star_2.png b/android/app/src/main/res/drawable/ic_star_2.png deleted file mode 100644 index f7bee9b1d..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_2.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_star_3.png b/android/app/src/main/res/drawable/ic_star_3.png deleted file mode 100644 index 330aefbac..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_3.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_star_4.png b/android/app/src/main/res/drawable/ic_star_4.png deleted file mode 100644 index 4e9f58dfa..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_4.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_star_5.png b/android/app/src/main/res/drawable/ic_star_5.png deleted file mode 100644 index aa5707ea7..000000000 Binary files a/android/app/src/main/res/drawable/ic_star_5.png and /dev/null differ diff --git a/android/app/src/main/res/layout/activity_controller_mapping.xml b/android/app/src/main/res/layout/activity_controller_mapping.xml deleted file mode 100644 index de6591a20..000000000 --- a/android/app/src/main/res/layout/activity_controller_mapping.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_emulation.xml b/android/app/src/main/res/layout/activity_emulation.xml deleted file mode 100644 index 12493cbca..000000000 --- a/android/app/src/main/res/layout/activity_emulation.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_game_directories.xml b/android/app/src/main/res/layout/activity_game_directories.xml deleted file mode 100644 index 54bfc2181..000000000 --- a/android/app/src/main/res/layout/activity_game_directories.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index c9dc2d100..000000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_memory_card_editor.xml b/android/app/src/main/res/layout/activity_memory_card_editor.xml deleted file mode 100644 index fa471163a..000000000 --- a/android/app/src/main/res/layout/activity_memory_card_editor.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_achievement_list.xml b/android/app/src/main/res/layout/fragment_achievement_list.xml deleted file mode 100644 index d1a64a0de..000000000 --- a/android/app/src/main/res/layout/fragment_achievement_list.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_achievements_login.xml b/android/app/src/main/res/layout/fragment_achievements_login.xml deleted file mode 100644 index 5ab32fd3c..000000000 --- a/android/app/src/main/res/layout/fragment_achievements_login.xml +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - - - - - - - - - - -