Skip to content

New XSharedPreferences

Jim Wu edited this page Jan 25, 2021 · 2 revisions

New XSharedPreferences

Since EdXposed API 93, a new XSharedPreferences is provided targeted for modules with sdk > 27. Modules developers can enable this new XSharedPreferences support by modifying the meta-data of your modules to set xposedminversion to 93 or above, or add xposedsharedprefs meta-data.

For the module

When enabled, EdXposed will automatically hook ContextImpl.getPreferencesDir() so that your Context.getSharedPreferences and PreferenceFragment could work without further modification. Also, EdXposed hooks ContextImpl.checkMode(int) so that you can easily get a world readable shared preference by passing Context.MODE_WORLD_READABLE to Context.getSharedPreferences.

If you are using xposedsharedprefs meta-data to indicate your module will use this new feature, you probably want to ensure this feature is working properly. The following code snippet will do the magic:

Java
SharedPreferences pref;
try {
    pref = context.getSharedPreferences(MY_PREF_NAME, Context.MODE_WORLD_READABLE);
} catch (SecurityException ignored) {
    // The new XSharedPreferences is not enabled or module's not loading
    pref = null; // other fallback, if any
}
Kotlin
val pref = try {
    context.getSharedPreferences(MY_PREF_NAME, Context.MODE_WORLD_READABLE)
} catch (e: SecurityException) {
    // The new XSharedPreferences is not enabled or module's not loading
    null // other fallback, if any
}

Notice that EdXposed will help you to make the preference world readable if and only if you are getting preference in mode Context.MODE_WORLD_READABLE.

For the hooked app

To read the preference from the hooked app, you can simply use XSharedPreferences(String packageName) or XSharedPreferences(String packageName, String prefFileName) to retrieve the preference. Notice that you cannot use the XSharedPreferences(File prefFile) because the preference file is stored in a random directory.

You should check the permission before you read anything from the module. You should also load the preference if you need it. Try to split the preference file to multiple files for different purposes. Here's an example:

Java
public class XposedInit implements IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources {

    private static SharedPreferences getPref(String path) {
        SharedPreferences pref = new XSharedPreferences(BuildConfig.APPLICATION_ID, path);
        return pref.getFile().canRead() ? pref : null;
    }

    @Override
    public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) {
        SharedPreferences pref = getPref("zygote_conf");
        if (pref != null) {
            // do things with it
        } else {
            Log.e(TAG, "Cannot load pref for zygote properly");
        }
    }

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
        SharedPreferences pref;
        switch (lpparam.packageName) {
            case PACKAGE_NAME_A:
                pref = getPref("a_conf");
                if (pref != null) {
                    // do things with it for A
                } else {
                    Log.e(TAG, "Cannot load pref for A properly");
                }
                break;
            case PACKAGE_NAME_B:
                pref = getPref("b_conf");
                if (pref != null) {
                    // do things with it for B
                } else {
                    Log.e(TAG, "Cannot load pref for B properly");
                }
                break;
            case BuildConfig.APPLICATION_ID:
                // hook myself
                break;
            default:
                // skip
        }
    }

    @Override
    public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) {
        // similar to handleLoadPackage
    }
}
Kotlin
class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources {

    companion object {
        fun getPref(path: String) : SharedPreferences? {
            val pref = XSharedPreferences(BuildConfig.APPLICATION_ID, path)
            return if(pref.file.canRead()) pref else null
        }

        // lazy loads when needed
        val prefForZygote by lazy { getPref("zygote_conf") }

        // lazy loads when needed
        val prefForA by lazy { getPref("a_conf") }

        // lazy loads when needed
        val prefForB by lazy { getPref("b_conf") }
    }

    override fun initZygote(startupParam: StartupParam) {
        prefForZygote?.let {
            // do things with it
        } ?: Log.e(TAG, "Cannot load pref for zygote properly")
    }


    override fun handleLoadPackage(lpparam: LoadPackageParam) {
        when(lpparam.packageName) {
            PACKAGE_NAME_A -> {
                prefForA?.let {
                    // do things with it for A
                } ?: Log.e(TAG, "Cannot load pref for A properly")
            }

            PACKAGE_NAME_B -> {
                prefForB?.let {
                    // do things with it for B
                } ?: Log.e(TAG, "Cannot load pref for B properly")

            }

            BuildConfig.APPLICATION_ID -> {
                // hook myself
            }

            default -> {
                // skip
            }
        }
    }

    override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
        // similar to handleLoadPackage
    }
}
Clone this wiki locally