From 59b9e4b2eff3e2503c192886aa61a5e4f3586364 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 29 Dec 2020 00:45:40 +1000 Subject: [PATCH] Android: Hook up most of the settings interface mutators --- .../app/src/cpp/android_host_interface.cpp | 5 + android/app/src/cpp/android_host_interface.h | 1 + .../src/cpp/android_settings_interface.cpp | 147 ++++++++++++++++-- .../app/src/cpp/android_settings_interface.h | 13 ++ .../duckstation/AndroidHostInterface.java | 1 + .../duckstation/PreferenceHelpers.java | 75 +++++++++ 6 files changed, 229 insertions(+), 13 deletions(-) create mode 100644 android/app/src/main/java/com/github/stenzek/duckstation/PreferenceHelpers.java diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index b9a7b5155..2dd84bb63 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -81,6 +81,11 @@ std::string JStringToString(JNIEnv* env, jstring str) 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); diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index 58e572e64..59c7c297a 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -108,6 +108,7 @@ 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(); } // namespace AndroidHelpers template diff --git a/android/app/src/cpp/android_settings_interface.cpp b/android/app/src/cpp/android_settings_interface.cpp index 9da95b295..ddc4751d2 100644 --- a/android/app/src/cpp/android_settings_interface.cpp +++ b/android/app/src/cpp/android_settings_interface.cpp @@ -16,14 +16,21 @@ 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_set && m_get_default_shared_preferences); + 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)); - Assert(m_set_class); + 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); @@ -47,6 +54,18 @@ AndroidSettingsInterface::AndroidSettingsInterface(jobject java_context) 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() @@ -54,10 +73,14 @@ 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); } void AndroidSettingsInterface::Clear() @@ -179,34 +202,102 @@ std::string AndroidSettingsInterface::GetStringValue(const char* section, const 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_ErrorPrintf("SetIntValue(\"%s\", \"%s\", %d) not implemented", section, key, 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_ErrorPrintf("SetFloatValue(\"%s\", \"%s\", %f) not implemented", section, key, 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_ErrorPrintf("SetBoolValue(\"%s\", \"%s\", %u) not implemented", section, key, static_cast(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_ErrorPrintf("SetStringValue(\"%s\", \"%s\", \"%s\") not implemented", section, key, 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_ErrorPrintf("DeleteValue(\"%s\", \"%s\") not implemented", section, 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_ErrorPrintf("ClearSection(\"%s\") not implemented", 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) @@ -251,17 +342,47 @@ std::vector AndroidSettingsInterface::GetStringList(const char* sec void AndroidSettingsInterface::SetStringList(const char* section, const char* key, const std::vector& items) { - Log_ErrorPrintf("SetStringList(\"%s\", \"%s\") not implemented", section, key); + 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_ErrorPrintf("RemoveFromStringList(\"%s\", \"%s\", \"%s\") not implemented", section, key, item); - return false; + 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_ErrorPrintf("AddToStringList(\"%s\", \"%s\", \"%s\") not implemented", section, key, item); - return false; + 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 index baddbf290..b4550ddcf 100644 --- a/android/app/src/cpp/android_settings_interface.h +++ b/android/app/src/cpp/android_settings_interface.h @@ -28,13 +28,26 @@ public: 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/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index c10018697..92ad6c7e6 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -1,6 +1,7 @@ package com.github.stenzek.duckstation; import android.content.Context; +import android.content.SharedPreferences; import android.content.res.AssetManager; import android.os.Environment; import android.util.Log; 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 new file mode 100644 index 000000000..387b139a4 --- /dev/null +++ b/android/app/src/main/java/com/github/stenzek/duckstation/PreferenceHelpers.java @@ -0,0 +1,75 @@ +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<>(); + + 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; + + 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(); + } +}