From 70f967944cf28cfb3b14d4c67143bb9993194ea9 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Thu, 3 Dec 2020 04:22:16 +0800 Subject: [PATCH] New XSharedPreferences --- .../riru/edxp/_hooker/impl/HandleBindApp.java | 3 - .../riru/edxp/proxy/NormalProxy.java | 2 + .../android/xposed/XSharedPreferences.java | 520 ++++++++++-------- .../de/robv/android/xposed/XposedInit.java | 7 + 4 files changed, 289 insertions(+), 243 deletions(-) diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/_hooker/impl/HandleBindApp.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/_hooker/impl/HandleBindApp.java index 094172b1..ffe2d0d8 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/_hooker/impl/HandleBindApp.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/_hooker/impl/HandleBindApp.java @@ -8,8 +8,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.XResources; -import android.graphics.Bitmap; -import android.os.UserHandle; import com.elderdrivers.riru.edxp.config.ConfigManager; import com.elderdrivers.riru.edxp.util.Hookers; @@ -18,7 +16,6 @@ import com.elderdrivers.riru.edxp.util.Utils; import java.io.File; import de.robv.android.xposed.XC_MethodHook; -import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java index 92d26252..c1bc257a 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java @@ -4,6 +4,7 @@ import com.elderdrivers.riru.edxp.config.ConfigManager; import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter; import de.robv.android.xposed.SELinuxHelper; +import de.robv.android.xposed.XposedInit; import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix; @@ -55,6 +56,7 @@ public class NormalProxy extends BaseProxy { private void forkPostCommon(int pid, boolean isSystem, String appDataDir, String niceName) { ConfigManager.appDataDir = appDataDir; ConfigManager.niceName = niceName; + XposedInit.prefsBasePath = ConfigManager.getPrefsPath(""); mRouter.prepare(isSystem); mRouter.onEnterChildProcess(); mRouter.loadModulesSafely(true); diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XSharedPreferences.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XSharedPreferences.java index 749809db..dea22e89 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XSharedPreferences.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XSharedPreferences.java @@ -1,8 +1,11 @@ package de.robv.android.xposed; import android.annotation.SuppressLint; +import android.app.ActivityThread; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Environment; import android.preference.PreferenceManager; import android.util.Log; @@ -28,270 +31,307 @@ import de.robv.android.xposed.services.FileResult; * @deprecated in Android Pie or later was lost by Google, will not remove */ public final class XSharedPreferences implements SharedPreferences { - private static final String TAG = "XSharedPreferences"; - private final File mFile; - private final String mFilename; - private Map mMap; - private boolean mLoaded = false; - private long mLastModified; - private long mFileSize; + private static final String TAG = "XSharedPreferences"; + private final File mFile; + private final String mFilename; + private Map mMap; + private boolean mLoaded = false; + private long mLastModified; + private long mFileSize; - /** - * Read settings from the specified file. - * @param prefFile The file to read the preferences from. - */ - public XSharedPreferences(File prefFile) { - mFile = prefFile; - mFilename = mFile.getAbsolutePath(); - startLoadFromDisk(); - } + /** + * Read settings from the specified file. + * + * @param prefFile The file to read the preferences from. + */ + public XSharedPreferences(File prefFile) { + mFile = prefFile; + mFilename = mFile.getAbsolutePath(); + startLoadFromDisk(); + } - /** - * Read settings from the default preferences for a package. - * These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}. - * @param packageName The package name. - */ - public XSharedPreferences(String packageName) { - this(packageName, packageName + "_preferences"); - } + /** + * Read settings from the default preferences for a package. + * These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}. + * + * @param packageName The package name. + */ + public XSharedPreferences(String packageName) { + this(packageName, packageName + "_preferences"); + } - /** - * Read settings from a custom preferences file for a package. - * These preferences are returned by {@link Context#getSharedPreferences(String, int)}. - * @param packageName The package name. - * @param prefFileName The file name without ".xml". - */ - public XSharedPreferences(String packageName, String prefFileName) { - mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml"); - mFilename = mFile.getAbsolutePath(); - startLoadFromDisk(); - } + /** + * Read settings from a custom preferences file for a package. + * These preferences are returned by {@link Context#getSharedPreferences(String, int)}. + * + * @param packageName The package name. + * @param prefFileName The file name without ".xml". + */ + public XSharedPreferences(String packageName, String prefFileName) { + boolean newModule = false; + Set modules = XposedInit.getLoadedModules(); + for (String m : modules) { + if (m.contains("/" + packageName + "-")) { + PackageInfo packageInfo = ((Context) ActivityThread.currentActivityThread().getSystemContext()).getPackageManager().getPackageArchiveInfo(m, PackageManager.GET_META_DATA); + newModule = packageInfo != null && packageInfo.applicationInfo != null && packageInfo.applicationInfo.metaData.getBoolean("xposedmodule") && packageInfo.applicationInfo.metaData.getInt("xposedminversion", -1) > 92; + } + } + if (newModule && XposedInit.prefsBasePath != null) { + mFile = new File(XposedInit.prefsBasePath, packageName + "/" + prefFileName + ".xml"); + } else { + mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml"); + } + mFilename = mFile.getAbsolutePath(); + startLoadFromDisk(); + } - /** - * Tries to make the preferences file world-readable. - * - *

Warning: This is only meant to work around permission "fix" functions that are part - * of some recoveries. It doesn't replace the need to open preferences with {@code MODE_WORLD_READABLE} - * in the module's UI code. Otherwise, Android will set stricter permissions again during the next save. - * - *

This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled. - * - * @return {@code true} in case the file could be made world-readable. - */ - @SuppressLint("SetWorldReadable") - public boolean makeWorldReadable() { - if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess()) - return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway. + /** + * Tries to make the preferences file world-readable. + * + *

Warning: This is only meant to work around permission "fix" functions that are part + * of some recoveries. It doesn't replace the need to open preferences with {@code MODE_WORLD_READABLE} + * in the module's UI code. Otherwise, Android will set stricter permissions again during the next save. + * + *

This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled. + * + * @return {@code true} in case the file could be made world-readable. + */ + @SuppressLint("SetWorldReadable") + public boolean makeWorldReadable() { + if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess()) + return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway. - if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist. - return false; + if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist. + return false; - return mFile.setReadable(true, false); - } + return mFile.setReadable(true, false); + } - /** - * Returns the file that is backing these preferences. - * - *

Warning: The file might not be accessible directly. - */ - public File getFile() { - return mFile; - } + /** + * Returns the file that is backing these preferences. + * + *

Warning: The file might not be accessible directly. + */ + public File getFile() { + return mFile; + } - private void startLoadFromDisk() { - synchronized (this) { - mLoaded = false; - } - new Thread("XSharedPreferences-load") { - @Override - public void run() { - synchronized (XSharedPreferences.this) { - loadFromDiskLocked(); - } - } - }.start(); - } + private void startLoadFromDisk() { + synchronized (this) { + mLoaded = false; + } + new Thread("XSharedPreferences-load") { + @Override + public void run() { + synchronized (XSharedPreferences.this) { + loadFromDiskLocked(); + } + } + }.start(); + } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private void loadFromDiskLocked() { - if (mLoaded) { - return; - } + @SuppressWarnings({"rawtypes", "unchecked"}) + private void loadFromDiskLocked() { + if (mLoaded) { + return; + } - Map map = null; - FileResult result = null; - try { - result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified); - if (result.stream != null) { - map = XmlUtils.readMapXml(result.stream); - result.stream.close(); - } else { - // The file is unchanged, keep the current values - map = mMap; - } - } catch (XmlPullParserException e) { - Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); - } catch (FileNotFoundException ignored) { - // SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist - } catch (IOException e) { - Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); - } finally { - if (result != null && result.stream != null) { - try { - result.stream.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } + Map map = null; + FileResult result = null; + try { + result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified); + if (result.stream != null) { + map = XmlUtils.readMapXml(result.stream); + result.stream.close(); + } else { + // The file is unchanged, keep the current values + map = mMap; + } + } catch (XmlPullParserException e) { + Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); + } catch (FileNotFoundException ignored) { + // SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist + } catch (IOException e) { + Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); + } finally { + if (result != null && result.stream != null) { + try { + result.stream.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } - mLoaded = true; - if (map != null) { - mMap = map; - mLastModified = result.mtime; - mFileSize = result.size; - } else { - mMap = new HashMap<>(); - } - notifyAll(); - } + mLoaded = true; + if (map != null) { + mMap = map; + mLastModified = result.mtime; + mFileSize = result.size; + } else { + mMap = new HashMap<>(); + } + notifyAll(); + } - /** - * Reload the settings from file if they have changed. - * - *

Warning: With enforcing SELinux, this call might be quite expensive. - */ - public synchronized void reload() { - if (hasFileChanged()) - startLoadFromDisk(); - } + /** + * Reload the settings from file if they have changed. + * + *

Warning: With enforcing SELinux, this call might be quite expensive. + */ + public synchronized void reload() { + if (hasFileChanged()) + startLoadFromDisk(); + } - /** - * Check whether the file has changed since the last time it has been loaded. - * - *

Warning: With enforcing SELinux, this call might be quite expensive. - */ - public synchronized boolean hasFileChanged() { - try { - FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename); - return mLastModified != result.mtime || mFileSize != result.size; - } catch (FileNotFoundException ignored) { - // SharedPreferencesImpl doesn't log anything in case the file doesn't exist - return true; - } catch (IOException e) { - Log.w(TAG, "hasFileChanged", e); - return true; - } - } + /** + * Check whether the file has changed since the last time it has been loaded. + * + *

Warning: With enforcing SELinux, this call might be quite expensive. + */ + public synchronized boolean hasFileChanged() { + try { + FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename); + return mLastModified != result.mtime || mFileSize != result.size; + } catch (FileNotFoundException ignored) { + // SharedPreferencesImpl doesn't log anything in case the file doesn't exist + return true; + } catch (IOException e) { + Log.w(TAG, "hasFileChanged", e); + return true; + } + } - private void awaitLoadedLocked() { - while (!mLoaded) { - try { - wait(); - } catch (InterruptedException unused) { - } - } - } + private void awaitLoadedLocked() { + while (!mLoaded) { + try { + wait(); + } catch (InterruptedException unused) { + } + } + } - /** @hide */ - @Override - public Map getAll() { - synchronized (this) { - awaitLoadedLocked(); - return new HashMap<>(mMap); - } - } + /** + * @hide + */ + @Override + public Map getAll() { + synchronized (this) { + awaitLoadedLocked(); + return new HashMap<>(mMap); + } + } - /** @hide */ - @Override - public String getString(String key, String defValue) { - synchronized (this) { - awaitLoadedLocked(); - String v = (String)mMap.get(key); - return v != null ? v : defValue; - } - } + /** + * @hide + */ + @Override + public String getString(String key, String defValue) { + synchronized (this) { + awaitLoadedLocked(); + String v = (String) mMap.get(key); + return v != null ? v : defValue; + } + } - /** @hide */ - @Override - @SuppressWarnings("unchecked") - public Set getStringSet(String key, Set defValues) { - synchronized (this) { - awaitLoadedLocked(); - Set v = (Set) mMap.get(key); - return v != null ? v : defValues; - } - } + /** + * @hide + */ + @Override + @SuppressWarnings("unchecked") + public Set getStringSet(String key, Set defValues) { + synchronized (this) { + awaitLoadedLocked(); + Set v = (Set) mMap.get(key); + return v != null ? v : defValues; + } + } - /** @hide */ - @Override - public int getInt(String key, int defValue) { - synchronized (this) { - awaitLoadedLocked(); - Integer v = (Integer)mMap.get(key); - return v != null ? v : defValue; - } - } + /** + * @hide + */ + @Override + public int getInt(String key, int defValue) { + synchronized (this) { + awaitLoadedLocked(); + Integer v = (Integer) mMap.get(key); + return v != null ? v : defValue; + } + } - /** @hide */ - @Override - public long getLong(String key, long defValue) { - synchronized (this) { - awaitLoadedLocked(); - Long v = (Long)mMap.get(key); - return v != null ? v : defValue; - } - } + /** + * @hide + */ + @Override + public long getLong(String key, long defValue) { + synchronized (this) { + awaitLoadedLocked(); + Long v = (Long) mMap.get(key); + return v != null ? v : defValue; + } + } - /** @hide */ - @Override - public float getFloat(String key, float defValue) { - synchronized (this) { - awaitLoadedLocked(); - Float v = (Float)mMap.get(key); - return v != null ? v : defValue; - } - } + /** + * @hide + */ + @Override + public float getFloat(String key, float defValue) { + synchronized (this) { + awaitLoadedLocked(); + Float v = (Float) mMap.get(key); + return v != null ? v : defValue; + } + } - /** @hide */ - @Override - public boolean getBoolean(String key, boolean defValue) { - synchronized (this) { - awaitLoadedLocked(); - Boolean v = (Boolean)mMap.get(key); - return v != null ? v : defValue; - } - } + /** + * @hide + */ + @Override + public boolean getBoolean(String key, boolean defValue) { + synchronized (this) { + awaitLoadedLocked(); + Boolean v = (Boolean) mMap.get(key); + return v != null ? v : defValue; + } + } - /** @hide */ - @Override - public boolean contains(String key) { - synchronized (this) { - awaitLoadedLocked(); - return mMap.containsKey(key); - } - } + /** + * @hide + */ + @Override + public boolean contains(String key) { + synchronized (this) { + awaitLoadedLocked(); + return mMap.containsKey(key); + } + } - /** @deprecated Not supported by this implementation. */ - @Deprecated - @Override - public Editor edit() { - throw new UnsupportedOperationException("read-only implementation"); - } + /** + * @deprecated Not supported by this implementation. + */ + @Deprecated + @Override + public Editor edit() { + throw new UnsupportedOperationException("read-only implementation"); + } - /** @deprecated Not supported by this implementation. */ - @Deprecated - @Override - public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - throw new UnsupportedOperationException("listeners are not supported in this implementation"); - } + /** + * @deprecated Not supported by this implementation. + */ + @Deprecated + @Override + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + throw new UnsupportedOperationException("listeners are not supported in this implementation"); + } - /** @deprecated Not supported by this implementation. */ - @Deprecated - @Override - public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - throw new UnsupportedOperationException("listeners are not supported in this implementation"); - } + /** + * @deprecated Not supported by this implementation. + */ + @Deprecated + @Override + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + throw new UnsupportedOperationException("listeners are not supported in this implementation"); + } } diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java index f51831cc..5847208f 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java @@ -69,6 +69,7 @@ public final class XposedInit { private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication"; public static volatile boolean disableResources = false; private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"}; + public static String prefsBasePath = null; private XposedInit() { } @@ -310,6 +311,12 @@ public final class XposedInit { // @GuardedBy("moduleLoadLock") private static final ArraySet loadedModules = new ArraySet<>(); + public static ArraySet getLoadedModules() { + synchronized (moduleLoadLock) { + return loadedModules; + } + } + public static boolean loadModules(boolean callInitZygote) throws IOException { boolean hasLoaded = !modulesLoaded.compareAndSet(false, true); if (hasLoaded) {