From 1541db6427349bd5cb646be7dd174f661a1e7cac Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Mon, 18 Apr 2022 01:11:02 +0800 Subject: [PATCH] Support PreferenceFragmentCompat in New XSharePrefs --- docs/api/document.md | 2 + docs/api/public/ModulePreferenceFragment.md | 81 +++++++++++++ docs/api/public/YukiHookModulePrefs.md | 18 +++ .../hook/xposed/prefs/YukiHookModulePrefs.kt | 22 +++- .../prefs/ui/ModulePreferenceFragment.kt | 109 ++++++++++++++++++ 5 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 docs/api/public/ModulePreferenceFragment.md create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/ui/ModulePreferenceFragment.kt diff --git a/docs/api/document.md b/docs/api/document.md index 8a3e87c8..197b2cd1 100644 --- a/docs/api/document.md +++ b/docs/api/document.md @@ -16,6 +16,8 @@ [filename](public/YukiHookModulePrefs.md ':include') +[filename](public/ModulePreferenceFragment.md ':include') + [filename](public/PrefsData.md ':include') [filename](public/ModuleApplication.md ':include') diff --git a/docs/api/public/ModulePreferenceFragment.md b/docs/api/public/ModulePreferenceFragment.md new file mode 100644 index 00000000..3402ad13 --- /dev/null +++ b/docs/api/public/ModulePreferenceFragment.md @@ -0,0 +1,81 @@ +## ModulePreferenceFragment [class] + +```kotlin +abstract class ModulePreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener +``` + +**变更记录** + +`v1.0.78` `新增` + +**功能描述** + +> 这是对使用 `YukiHookAPI` Xposed 模块实现中的一个扩展功能。 + +此类接管了 `PreferenceFragmentCompat` 并对其实现了 Sp 存储在 Xposed 模块中的全局可读可写。 + +在你使用 `PreferenceFragmentCompat` 的实例中,将继承对象换成此类。 + +然后请将重写方法由 `onCreatePreferences` 替换为 `onCreatePreferencesInModuleApp` 即可。 + +**功能示例** + +使用 `ModulePreferenceFragment` 创建一个 `PreferenceFragmentCompat` 对象。 + +> 示例如下 + +```kotlin +class SettingsFragment : ModulePreferenceFragment() { + + override fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.settings_preferences, rootKey) + // Your code here. + } +} +``` + +其余用法与 `PreferenceFragmentCompat` 保持一致。 + +### onCreatePreferencesInModuleApp [method] + +```kotlin +abstract fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) +``` + +**变更记录** + +`v1.0.78` `新增` + +**功能描述** + +> 对接原始方法 `onCreatePreferences`。 + +### onSharedPreferenceChanged [method] + +```kotlin +override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) +``` + +**变更记录** + +`v1.0.78` `新增` + +**功能描述** + +> 实现了 `SharedPreferences.OnSharedPreferenceChangeListener` 的原生监听功能。 + +**功能示例** + +!> 在使用 `onSharedPreferenceChanged` 时请注意保留 super 方法。 + +> 示例如下 + +```kotlin +class SettingsFragment : ModulePreferenceFragment() { + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + super.onSharedPreferenceChanged(sharedPreferences, key) + // Your code here. + } +} +``` \ No newline at end of file diff --git a/docs/api/public/YukiHookModulePrefs.md b/docs/api/public/YukiHookModulePrefs.md index 5281e239..b6a66edf 100644 --- a/docs/api/public/YukiHookModulePrefs.md +++ b/docs/api/public/YukiHookModulePrefs.md @@ -26,6 +26,24 @@ class YukiHookModulePrefs(private val context: Context?) !> 当你在 Xposed 模块中存取数据的时候 `context` 必须不能是空的。 +若你正在使用 `PreferenceFragmentCompat`,请迁移到 `ModulePreferenceFragment` 以适配上述功能特性。 + +### isRunInNewXShareMode [field] + +```kotlin +val isRunInNewXShareMode: Boolean +``` + +**变更记录** + +`v1.0.78` `新增` + +**功能描述** + +> 获取 `YukiHookModulePrefs ` 是否正处于 EdXposed/LSPosed 的最高权限运行。 + +前提条件为当前 Xposed 模块已被激活。 + ### name [method] ```kotlin diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt index 28be90a6..1302cda7 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt @@ -31,10 +31,12 @@ package com.highcapable.yukihookapi.hook.xposed.prefs import android.content.Context import android.content.SharedPreferences +import androidx.preference.PreferenceFragmentCompat import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookXposedBridge import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData +import com.highcapable.yukihookapi.hook.xposed.prefs.ui.ModulePreferenceFragment import de.robv.android.xposed.XSharedPreferences import java.io.File @@ -53,6 +55,8 @@ import java.io.File * * - ❗当你在模块中存取数据的时候 [context] 必须不能是空的 * + * - 若你正在使用 [PreferenceFragmentCompat] - 请迁移到 [ModulePreferenceFragment] 以适配上述功能特性 + * * - 详情请参考 [API 文档 - YukiHookModulePrefs](https://fankes.github.io/YukiHookAPI/#/api/document?id=yukihookmoduleprefs-class) * @param context 上下文实例 - 默认空 */ @@ -100,8 +104,8 @@ class YukiHookModulePrefs(private val context: Context? = null) { /** 是否使用键值缓存 */ private var isUsingKeyValueCache = YukiHookAPI.Configs.isEnableModulePrefsCache - /** 是否为新版存储方式 EdXposed/LSPosed */ - private var isNewXSharePrefsMode = false + /** 是否使用新版存储方式 EdXposed/LSPosed */ + private var isUsingNewXSharePrefs = false /** 检查 API 装载状态 */ private fun checkApi() { @@ -128,19 +132,27 @@ class YukiHookModulePrefs(private val context: Context? = null) { private val sPref get() = try { checkApi() - context?.getSharedPreferences(prefsName, Context.MODE_WORLD_READABLE).also { isNewXSharePrefsMode = true } + context?.getSharedPreferences(prefsName, Context.MODE_WORLD_READABLE).also { isUsingNewXSharePrefs = true } ?: error("If you want to use module prefs, you must set the context instance first") } catch (_: Throwable) { checkApi() - context?.getSharedPreferences(prefsName, Context.MODE_PRIVATE).also { isNewXSharePrefsMode = false } + context?.getSharedPreferences(prefsName, Context.MODE_PRIVATE).also { isUsingNewXSharePrefs = false } ?: error("If you want to use module prefs, you must set the context instance first") } /** 设置全局可读可写 */ private fun makeWorldReadable() = runCatching { - if (isNewXSharePrefsMode.not()) makeWorldReadable(context, prefsFileName = "${prefsName}.xml") + if (isUsingNewXSharePrefs.not()) makeWorldReadable(context, prefsFileName = "${prefsName}.xml") } + /** + * 获取 [YukiHookModulePrefs] 是否正处于 EdXposed/LSPosed 的最高权限运行 + * + * - 前提条件为当前 Xposed 模块已被激活 + * @return [Boolean] 仅限在模块中判断 - 在宿主 [XSharedPreferences] 环境中始终返回 false + */ + val isRunInNewXShareMode get() = isUsingNewXSharePrefs + /** * 自定义 Sp 存储名称 * @param name 自定义的 Sp 存储名称 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/ui/ModulePreferenceFragment.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/ui/ModulePreferenceFragment.kt new file mode 100644 index 00000000..2e540d0f --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/ui/ModulePreferenceFragment.kt @@ -0,0 +1,109 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/4/17. + */ +@file:Suppress("WorldReadableFiles", "DEPRECATION") + +package com.highcapable.yukihookapi.hook.xposed.prefs.ui + +import android.app.Activity +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import androidx.annotation.CallSuper +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager +import androidx.preference.PreferenceScreen +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs + +/** + * 这是对使用 [YukiHookAPI] Xposed 模块实现中的一个扩展功能 + * + * 此类接管了 [PreferenceFragmentCompat] 并对其实现了 Sp 存储在 Xposed 模块中的全局可读可写 + * + * 在你使用 [PreferenceFragmentCompat] 的实例中 - 将继承对象换成此类 + * + * 然后请将重写方法由 [onCreatePreferences] 替换为 [onCreatePreferencesInModuleApp] 即可 + * + * 详情请参考 [ModulePreferenceFragment](https://fankes.github.io/YukiHookAPI/#/api/document?id=modulepreferencefragment-class) + */ +abstract class ModulePreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { + + /** + * 获得 Sp 存储名称 + * @return [String] + */ + private val prefsName get() = "${activity?.packageName}_preferences" + + /** + * 获取当前 [Fragment] 绑定的 [Activity] + * @return [Activity] + * @throws IllegalStateException 如果 [Fragment] 已被销毁或未正确装载 + */ + private val currentActivity get() = requireActivity() + + /** + * 获取应用默认的 [SharedPreferences] + * @return [SharedPreferences] + */ + private val currentSharedPrefs get() = PreferenceManager.getDefaultSharedPreferences(currentActivity) + + @CallSuper + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + currentSharedPrefs.registerOnSharedPreferenceChangeListener(this) + makeNewXShareReadableIfPossible() + onCreatePreferencesInModuleApp(savedInstanceState, rootKey) + } + + @CallSuper + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + makeNewXShareReadableIfPossible() + } + + @CallSuper + override fun onDestroy() { + currentSharedPrefs.unregisterOnSharedPreferenceChangeListener(this) + super.onDestroy() + } + + /** + * 对接原始方法 [onCreatePreferences] + * + * 请重写此方法以实现模块 Sp 存储的自动化设置全局可读可写数据操作 + * @param savedInstanceState If the fragment is being re-created from a previous saved state, this is the state. + * @param rootKey If non-null, this preference fragment should be rooted at the [PreferenceScreen] with this key. + */ + abstract fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) + + /** 设置自动适配模块 Sp 存储全局可读可写 */ + private fun makeNewXShareReadableIfPossible() = try { + currentActivity.getSharedPreferences(prefsName, Context.MODE_WORLD_READABLE) + } catch (_: Throwable) { + YukiHookModulePrefs.makeWorldReadable(currentActivity, prefsFileName = "$prefsName.xml") + } +} \ No newline at end of file