RemotePreferences

repository·master·Indexed 40 days ago

https://github.com/apsun/remotepreferences

An Android library that allows different applications to access each other's SharedPreferences via a ContentProvider proxy. It is designed to overcome limitations in Xposed module preference access, providing remote write access and avoiding silent failures. Features include fine-grained access control via checkAccess, support for device encrypted preferences (Direct Boot) on Android N+, and an optional strict mode to throw RemotePreferenceAccessException.

Tokens
1.7K
Snippets
7
Records
7
Agent score
0%

What's inside RemotePreferences

  1. Register RemotePreferenceProvider in AndroidManifest.xml

    master

    Add a <provider> entry to your AndroidManifest.xml. The android:authorities must match the authority string used in your RemotePreferenceProvider constructor, and android:exported must be set to true.

    <provider
        android:name=".MyPreferenceProvider"
        android:authorities="com.example.app.preferences"
        android:exported="true"/>
  2. Configure Device Encrypted Preferences (Direct Boot)

    master

    For Android N+ devices with file-based encryption, you may need to access preferences before the first unlock after reboot. To do this, follow these three steps:

    1. Modify the Provider: Use RemotePreferenceFile in the constructor to mark files as device protected.
    2. Update Manifest: Set android:directBootAware="true" on your <provider>.
    3. Update Owner App: The app owning the preferences must use createDeviceProtectedStorageContext() to access them.
    // 1. Provider setup
    public class MyPreferenceProvider extends RemotePreferenceProvider {
        public MyPreferenceProvider() {
            super("com.example.app.preferences", new RemotePreferenceFile[] {
                new RemotePreferenceFile("main_prefs", /* isDeviceProtected */ true)
            });
        }
    }
    
    // 2. Manifest
    <provider
        android:name=".MyPreferenceProvider"
        android:authorities="com.example.app.preferences"
        android:exported="true"
        android:directBootAware="true"/>
    
    // 3. Owner app access
    Context prefContext = context.createDeviceProtectedStorageContext();
    SharedPreferences prefs = prefContext.getSharedPreferences("main_prefs", MODE_PRIVATE);
  3. Expose SharedPreferences via RemotePreferenceProvider

    master

    To allow other apps to access your preferences, you must subclass RemotePreferenceProvider and implement a 0-argument constructor. This constructor calls super(authority, preferenceFiles) where authority is a unique string and preferenceFiles is an array of names of the files you wish to expose.

    public class MyPreferenceProvider extends RemotePreferenceProvider {
        public MyPreferenceProvider() {
            super("com.example.app.preferences", new String[] {"main_prefs"});
        }
    }
  4. Enable Strict Mode to catch RemotePreferenceAccessException

    master

    By default, RemotePreferences fails silently (returning default values) if the provider is inaccessible. To enable error detection, pass true as the fourth parameter to the RemotePreferences constructor. This will cause a RemotePreferenceAccessException to be thrown if the provider cannot be accessed, allowing you to handle it with a try-catch block.

    // Enable strict mode by passing 'true' as the 4th argument
    SharedPreferences prefs = new RemotePreferences(context, authority, prefFileName, true);
    
    try {
        int value = prefs.getInt("my_int_pref", 0);
        prefs.edit().putInt("my_int_pref", value + 1).apply();
    } catch (RemotePreferenceAccessException e) {
        // Handle the error (e.g., provider is disabled or inaccessible)
    }
  5. Access remote preferences using RemotePreferences

    master

    To read or write preferences from a different app, instantiate RemotePreferences with the target app's authority and the specific preference file name.

    Important Notes:

    • Do not use RemotePreferences within IXposedHookZygoteInit.initZygote. Defer loading to IXposedHookLoadPackage.handleLoadPackage instead.
    • If your code is running inside the app that owns the preferences, use standard context.getSharedPreferences(...) instead.
    • Preference keys cannot be null or an empty string ("").
    SharedPreferences prefs = new RemotePreferences(context, "com.example.app.preferences", "main_prefs");
    int value = prefs.getInt("my_int_pref", 0);
  6. Secure preferences using checkAccess

    master

    By default, all preferences have global read/write access. To restrict access, you can override the checkAccess method in your RemotePreferenceProvider subclass. This allows you to implement fine-grained logic based on the preference file, the specific key, the operation type (read vs write), and the calling package.

    Warning: When performing operations like getAll() or clear(), the prefKey parameter will be an empty string (""). If you are blacklisting keys, ensure you also handle the empty string case.

    @Override
    protected boolean checkAccess(String prefFileName, String prefKey, boolean write) {
        // Only allow read access
        if (write) {
            return false;
        }
    
        // Only allow access to certain preference keys
        if (!"my_pref_key".equals(prefKey)) {
            return false;
        }
    
        // Only allow access from certain apps
        if (!"com.example.otherapp".equals(getCallingPackage())) {
            return false;
        }
    
        return true;
    }