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