New XSharedPreferences

This commit is contained in:
LoveSy 2020-12-03 04:22:16 +08:00 committed by solohsu
parent 4672edc4ad
commit 70f967944c
4 changed files with 289 additions and 243 deletions

View File

@ -8,8 +8,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo; import android.content.res.CompatibilityInfo;
import android.content.res.XResources; 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.config.ConfigManager;
import com.elderdrivers.riru.edxp.util.Hookers; import com.elderdrivers.riru.edxp.util.Hookers;
@ -18,7 +16,6 @@ import com.elderdrivers.riru.edxp.util.Utils;
import java.io.File; import java.io.File;
import de.robv.android.xposed.XC_MethodHook; 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.XposedBridge;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.XposedInit;

View File

@ -4,6 +4,7 @@ import com.elderdrivers.riru.edxp.config.ConfigManager;
import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter; import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter;
import de.robv.android.xposed.SELinuxHelper; import de.robv.android.xposed.SELinuxHelper;
import de.robv.android.xposed.XposedInit;
import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix; 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) { private void forkPostCommon(int pid, boolean isSystem, String appDataDir, String niceName) {
ConfigManager.appDataDir = appDataDir; ConfigManager.appDataDir = appDataDir;
ConfigManager.niceName = niceName; ConfigManager.niceName = niceName;
XposedInit.prefsBasePath = ConfigManager.getPrefsPath("");
mRouter.prepare(isSystem); mRouter.prepare(isSystem);
mRouter.onEnterChildProcess(); mRouter.onEnterChildProcess();
mRouter.loadModulesSafely(true); mRouter.loadModulesSafely(true);

View File

@ -1,8 +1,11 @@
package de.robv.android.xposed; package de.robv.android.xposed;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment; import android.os.Environment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; 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 * @deprecated in Android Pie or later was lost by Google, will not remove
*/ */
public final class XSharedPreferences implements SharedPreferences { public final class XSharedPreferences implements SharedPreferences {
private static final String TAG = "XSharedPreferences"; private static final String TAG = "XSharedPreferences";
private final File mFile; private final File mFile;
private final String mFilename; private final String mFilename;
private Map<String, Object> mMap; private Map<String, Object> mMap;
private boolean mLoaded = false; private boolean mLoaded = false;
private long mLastModified; private long mLastModified;
private long mFileSize; private long mFileSize;
/** /**
* Read settings from the specified file. * Read settings from the specified file.
* @param prefFile The file to read the preferences from. *
*/ * @param prefFile The file to read the preferences from.
public XSharedPreferences(File prefFile) { */
mFile = prefFile; public XSharedPreferences(File prefFile) {
mFilename = mFile.getAbsolutePath(); mFile = prefFile;
startLoadFromDisk(); mFilename = mFile.getAbsolutePath();
} startLoadFromDisk();
}
/** /**
* Read settings from the default preferences for a package. * Read settings from the default preferences for a package.
* These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}. * These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}.
* @param packageName The package name. *
*/ * @param packageName The package name.
public XSharedPreferences(String packageName) { */
this(packageName, packageName + "_preferences"); public XSharedPreferences(String packageName) {
} this(packageName, packageName + "_preferences");
}
/** /**
* Read settings from a custom preferences file for a package. * Read settings from a custom preferences file for a package.
* These preferences are returned by {@link Context#getSharedPreferences(String, int)}. * These preferences are returned by {@link Context#getSharedPreferences(String, int)}.
* @param packageName The package name. *
* @param prefFileName The file name without ".xml". * @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"); public XSharedPreferences(String packageName, String prefFileName) {
mFilename = mFile.getAbsolutePath(); boolean newModule = false;
startLoadFromDisk(); Set<String> 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. * Tries to make the preferences file world-readable.
* *
* <p><strong>Warning:</strong> This is only meant to work around permission "fix" functions that are part * <p><strong>Warning:</strong> 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} * 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. * in the module's UI code. Otherwise, Android will set stricter permissions again during the next save.
* *
* <p>This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled. * <p>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. * @return {@code true} in case the file could be made world-readable.
*/ */
@SuppressLint("SetWorldReadable") @SuppressLint("SetWorldReadable")
public boolean makeWorldReadable() { public boolean makeWorldReadable() {
if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess()) 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. 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. if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
return false; return false;
return mFile.setReadable(true, false); return mFile.setReadable(true, false);
} }
/** /**
* Returns the file that is backing these preferences. * Returns the file that is backing these preferences.
* *
* <p><strong>Warning:</strong> The file might not be accessible directly. * <p><strong>Warning:</strong> The file might not be accessible directly.
*/ */
public File getFile() { public File getFile() {
return mFile; return mFile;
} }
private void startLoadFromDisk() { private void startLoadFromDisk() {
synchronized (this) { synchronized (this) {
mLoaded = false; mLoaded = false;
} }
new Thread("XSharedPreferences-load") { new Thread("XSharedPreferences-load") {
@Override @Override
public void run() { public void run() {
synchronized (XSharedPreferences.this) { synchronized (XSharedPreferences.this) {
loadFromDiskLocked(); loadFromDiskLocked();
} }
} }
}.start(); }.start();
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({"rawtypes", "unchecked"})
private void loadFromDiskLocked() { private void loadFromDiskLocked() {
if (mLoaded) { if (mLoaded) {
return; return;
} }
Map map = null; Map map = null;
FileResult result = null; FileResult result = null;
try { try {
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified); result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
if (result.stream != null) { if (result.stream != null) {
map = XmlUtils.readMapXml(result.stream); map = XmlUtils.readMapXml(result.stream);
result.stream.close(); result.stream.close();
} else { } else {
// The file is unchanged, keep the current values // The file is unchanged, keep the current values
map = mMap; map = mMap;
} }
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e);
} catch (FileNotFoundException ignored) { } catch (FileNotFoundException ignored) {
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist // SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e); Log.w(TAG, "getSharedPreferences failed for: " + mFilename, e);
} finally { } finally {
if (result != null && result.stream != null) { if (result != null && result.stream != null) {
try { try {
result.stream.close(); result.stream.close();
} catch (RuntimeException rethrown) { } catch (RuntimeException rethrown) {
throw rethrown; throw rethrown;
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
} }
mLoaded = true; mLoaded = true;
if (map != null) { if (map != null) {
mMap = map; mMap = map;
mLastModified = result.mtime; mLastModified = result.mtime;
mFileSize = result.size; mFileSize = result.size;
} else { } else {
mMap = new HashMap<>(); mMap = new HashMap<>();
} }
notifyAll(); notifyAll();
} }
/** /**
* Reload the settings from file if they have changed. * Reload the settings from file if they have changed.
* *
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive. * <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/ */
public synchronized void reload() { public synchronized void reload() {
if (hasFileChanged()) if (hasFileChanged())
startLoadFromDisk(); startLoadFromDisk();
} }
/** /**
* Check whether the file has changed since the last time it has been loaded. * Check whether the file has changed since the last time it has been loaded.
* *
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive. * <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/ */
public synchronized boolean hasFileChanged() { public synchronized boolean hasFileChanged() {
try { try {
FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename); FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename);
return mLastModified != result.mtime || mFileSize != result.size; return mLastModified != result.mtime || mFileSize != result.size;
} catch (FileNotFoundException ignored) { } catch (FileNotFoundException ignored) {
// SharedPreferencesImpl doesn't log anything in case the file doesn't exist // SharedPreferencesImpl doesn't log anything in case the file doesn't exist
return true; return true;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "hasFileChanged", e); Log.w(TAG, "hasFileChanged", e);
return true; return true;
} }
} }
private void awaitLoadedLocked() { private void awaitLoadedLocked() {
while (!mLoaded) { while (!mLoaded) {
try { try {
wait(); wait();
} catch (InterruptedException unused) { } catch (InterruptedException unused) {
} }
} }
} }
/** @hide */ /**
@Override * @hide
public Map<String, ?> getAll() { */
synchronized (this) { @Override
awaitLoadedLocked(); public Map<String, ?> getAll() {
return new HashMap<>(mMap); synchronized (this) {
} awaitLoadedLocked();
} return new HashMap<>(mMap);
}
}
/** @hide */ /**
@Override * @hide
public String getString(String key, String defValue) { */
synchronized (this) { @Override
awaitLoadedLocked(); public String getString(String key, String defValue) {
String v = (String)mMap.get(key); synchronized (this) {
return v != null ? v : defValue; awaitLoadedLocked();
} String v = (String) mMap.get(key);
} return v != null ? v : defValue;
}
}
/** @hide */ /**
@Override * @hide
@SuppressWarnings("unchecked") */
public Set<String> getStringSet(String key, Set<String> defValues) { @Override
synchronized (this) { @SuppressWarnings("unchecked")
awaitLoadedLocked(); public Set<String> getStringSet(String key, Set<String> defValues) {
Set<String> v = (Set<String>) mMap.get(key); synchronized (this) {
return v != null ? v : defValues; awaitLoadedLocked();
} Set<String> v = (Set<String>) mMap.get(key);
} return v != null ? v : defValues;
}
}
/** @hide */ /**
@Override * @hide
public int getInt(String key, int defValue) { */
synchronized (this) { @Override
awaitLoadedLocked(); public int getInt(String key, int defValue) {
Integer v = (Integer)mMap.get(key); synchronized (this) {
return v != null ? v : defValue; awaitLoadedLocked();
} Integer v = (Integer) mMap.get(key);
} return v != null ? v : defValue;
}
}
/** @hide */ /**
@Override * @hide
public long getLong(String key, long defValue) { */
synchronized (this) { @Override
awaitLoadedLocked(); public long getLong(String key, long defValue) {
Long v = (Long)mMap.get(key); synchronized (this) {
return v != null ? v : defValue; awaitLoadedLocked();
} Long v = (Long) mMap.get(key);
} return v != null ? v : defValue;
}
}
/** @hide */ /**
@Override * @hide
public float getFloat(String key, float defValue) { */
synchronized (this) { @Override
awaitLoadedLocked(); public float getFloat(String key, float defValue) {
Float v = (Float)mMap.get(key); synchronized (this) {
return v != null ? v : defValue; awaitLoadedLocked();
} Float v = (Float) mMap.get(key);
} return v != null ? v : defValue;
}
}
/** @hide */ /**
@Override * @hide
public boolean getBoolean(String key, boolean defValue) { */
synchronized (this) { @Override
awaitLoadedLocked(); public boolean getBoolean(String key, boolean defValue) {
Boolean v = (Boolean)mMap.get(key); synchronized (this) {
return v != null ? v : defValue; awaitLoadedLocked();
} Boolean v = (Boolean) mMap.get(key);
} return v != null ? v : defValue;
}
}
/** @hide */ /**
@Override * @hide
public boolean contains(String key) { */
synchronized (this) { @Override
awaitLoadedLocked(); public boolean contains(String key) {
return mMap.containsKey(key); synchronized (this) {
} awaitLoadedLocked();
} return mMap.containsKey(key);
}
}
/** @deprecated Not supported by this implementation. */ /**
@Deprecated * @deprecated Not supported by this implementation.
@Override */
public Editor edit() { @Deprecated
throw new UnsupportedOperationException("read-only implementation"); @Override
} public Editor edit() {
throw new UnsupportedOperationException("read-only implementation");
}
/** @deprecated Not supported by this implementation. */ /**
@Deprecated * @deprecated Not supported by this implementation.
@Override */
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { @Deprecated
throw new UnsupportedOperationException("listeners are not supported in this implementation"); @Override
} public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
/** @deprecated Not supported by this implementation. */ /**
@Deprecated * @deprecated Not supported by this implementation.
@Override */
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { @Deprecated
throw new UnsupportedOperationException("listeners are not supported in this implementation"); @Override
} public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
} }

View File

@ -69,6 +69,7 @@ public final class XposedInit {
private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication"; private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
public static volatile boolean disableResources = false; public static volatile boolean disableResources = false;
private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"}; private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"};
public static String prefsBasePath = null;
private XposedInit() { private XposedInit() {
} }
@ -310,6 +311,12 @@ public final class XposedInit {
// @GuardedBy("moduleLoadLock") // @GuardedBy("moduleLoadLock")
private static final ArraySet<String> loadedModules = new ArraySet<>(); private static final ArraySet<String> loadedModules = new ArraySet<>();
public static ArraySet<String> getLoadedModules() {
synchronized (moduleLoadLock) {
return loadedModules;
}
}
public static boolean loadModules(boolean callInitZygote) throws IOException { public static boolean loadModules(boolean callInitZygote) throws IOException {
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true); boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
if (hasLoaded) { if (hasLoaded) {