diff --git a/README.md b/README.md
index a535782..6cc371f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A drop-in solution for inter-app access to `SharedPreferences`.
## Installation
-1\. Add the dependency to your `build.gradle` file:
+Add the dependency to your `build.gradle` file:
```
repositories {
@@ -17,7 +17,10 @@ dependencies {
}
```
-2\. Subclass `RemotePreferenceProvider` and implement a 0-argument
+
+## RemotePreferences using Content Providers
+
+1\. Subclass `RemotePreferenceProvider` and implement a 0-argument
constructor which calls the super constructor with an authority
(e.g. `"com.example.app.preferences"`) and an array of
preference files to expose:
@@ -30,7 +33,7 @@ public class MyPreferenceProvider extends RemotePreferenceProvider {
}
```
-3\. Add the corresponding entry to `AndroidManifest.xml`, with
+2\. Add the corresponding entry to `AndroidManifest.xml`, with
`android:authorities` equal to the authority you picked in the
last step, and `android:exported` set to `true`:
@@ -41,7 +44,7 @@ last step, and `android:exported` set to `true`:
android:exported="true"/>
```
-4\. You're all set! To access your preferences, create a new
+3\. You're all set! To access your preferences, create a new
instance of `RemotePreferences` with the same authority and the
name of the preference file:
@@ -61,6 +64,61 @@ if your code is executing within the app that owns the preferences. Only use
Also note that your preference keys cannot be `null` or `""` (empty string).
+On Android 11 and above the receiving app must specify the contents it reads
+in it's `AndroidManifest.xml`.
+
+
+## IntentBridgedPreferences using Intents
+
+1\. Subclass `IntentBridgedPreferencesRequestedReceiver` and implement
+a 0-argument constructor which calls the super constructor with an action
+(e.g. "com.example.app.preferences") and the `SharedPreferences` instance to
+expose:
+
+```Java
+public class MyPreferencesRequestedReceiver extends IntenBridgedPreferencesRequestedReceiver {
+ public MyPreferencesRequestedReceiver() {
+ super("com.example.app.preferences", PreferenceManager.getDefaultSharedPreferences(AndroidAppHelper.currentApplication()));
+ }
+}
+```
+
+2\. Add the corresponding entry to `AndroidManifest.xml`, with
+`action` equal to the action you picked in the last step:
+
+```XML
+
+ * Propagates changes in the given {@link SharedPreferences} as intents + * to any other app on the device. + *
+ * + *+ * You must extend this class and declare a 0-argument constructor which + * calls the super constructor with the appropriate action and preferences + * parameters. + *
+ * + *+ * Remember to hold onto the created instance explicitely, e.g. through + * a member of an activity. This registers a listener on the given + * {@link SharedPreferences} and these are only weakly referenced. + *
+ */ +public class IntentBridgedPreferenceSender { + private final String mActionName; + private final Context mContext; + private final SharedPreferences.OnSharedPreferenceChangeListener mListener; + + /** + * Initializes this sender with the specified action and the given + * {@link SharedPreferences} as sources for changes and preferences. + * + * @param context The {@link Context} of this app. + * @param actionName The actionName of the action. + * @param sourcePreferences The {@link SharedPreferences} used as source. + */ + public IntentBridgedPreferenceSender(Context context, String actionName, SharedPreferences sourcePreferences) { + mContext = context; + mActionName = actionName; + mListener = this::onPreferenceChanged; + + sourcePreferences.registerOnSharedPreferenceChangeListener(mListener); + } + + private void onPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Intent intent = new Intent(mActionName + ".PREFERENCES"); + + IntentBridgedUtils.setAsIntentExtra(intent, key, sharedPreferences.getAll().get(key)); + + mContext.sendBroadcast(intent); + } +} diff --git a/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferences.java b/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferences.java new file mode 100644 index 0000000..72a57c0 --- /dev/null +++ b/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferences.java @@ -0,0 +1,124 @@ +package com.crossbowffs.remotepreferences; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Build; + +import java.util.Map; +import java.util.Set; + +/** + *+ * Provides a {@link SharedPreferences} compatible API to + * {@link IntentBridgedPreferenceSender} {@link IntentBridgedPreferencesRequestedReceiver}. + * See both classes for more information. + *
+ * + *+ * If you are reading preferences from the same context as the + * provider, you should not use this class; just access the + * {@link SharedPreferences} API as you would normally. + *
+ */ +public class IntentBridgedPreferences implements SharedPreferences { + private final SharedPreferences mCachedPreferences; + private final IntentFilter mPreferencesIntentFilter; + private final IntentBridgedPreferencesReceiver mPreferencesReceiver; + + /** + *+ * Initializes the intent bridged preferences with the specified + * authority. The authority must match the action tag defined in + * your manifest file. Only the specified preferences will be + * accessible through the provider. + *
+ * + *+ * As intents are asynchronous, this instance keeps a local cache + * of the last known values of the preferences. Upon creation + * a request to refresh this cache will be send, so the (old) cached + * values will be used until the new set of preferences is received. + *
+ * + * @param context The {@link Context} of this app. + * @param actionName The actionName of the action. + */ + public IntentBridgedPreferences(Context context, String actionName) { + mCachedPreferences = context.getSharedPreferences(actionName, Context.MODE_PRIVATE); + mPreferencesIntentFilter = new IntentFilter(actionName + ".PREFERENCES"); + mPreferencesReceiver = new IntentBridgedPreferencesReceiver(mCachedPreferences); + + context.registerReceiver(mPreferencesReceiver, mPreferencesIntentFilter); + + Intent intent = new Intent(actionName + ".PREFERENCES_REQUESTED") + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) { + intent = intent.setPackage(actionName); + } + + context.sendBroadcast(intent); + } + + @Override + public Map+ * Receives preferences as intents and sets the new value(s) into + * the given {@link SharedPreferences} insance. + *
+ */ +/* package */ class IntentBridgedPreferencesReceiver extends BroadcastReceiver { + private final SharedPreferences mSharedPreferences; + + public IntentBridgedPreferencesReceiver(SharedPreferences targetPreferences) { + mSharedPreferences = targetPreferences; + } + + @Override + public void onReceive(Context context, Intent intent) { + Bundle extras = intent.getExtras(); + + if (extras != null) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + + for (String key : extras.keySet()) { + Object value = extras.get(key); + + if (value instanceof String[]) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + editor.putStringSet(key, new HashSet<>(Arrays.asList((String[]) value))); + } + } else if (value instanceof String) { + editor.putString(key, (String) value); + } else if (value instanceof Long) { + editor.putLong(key, (long) value); + } else if (value instanceof Integer) { + editor.putInt(key, (int) value); + } else if (value instanceof Float) { + editor.putFloat(key, (float) value); + } else if (value instanceof Boolean) { + editor.putBoolean(key, (boolean) value); + } else if (value == null) { + editor.putString(key, (String) null); + } + } + + editor.commit(); + } + } +} diff --git a/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferencesRequestedReceiver.java b/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferencesRequestedReceiver.java new file mode 100644 index 0000000..0c24fc8 --- /dev/null +++ b/library/src/main/java/com/crossbowffs/remotepreferences/IntentBridgedPreferencesRequestedReceiver.java @@ -0,0 +1,42 @@ +package com.crossbowffs.remotepreferences; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; + +import java.util.Map; + +/** + *+ * Receives requests to send the preferences and responds with + * the full set of preferences being sent as intent. + *
+ */ +public abstract class IntentBridgedPreferencesRequestedReceiver extends BroadcastReceiver { + private final String mActionName; + private final SharedPreferences mSourcePreferences; + + /** + * Initializes this receiver with the action name and + * the given {@link SharedPreferences} as source for preferences. + * + * @param actionName The actionName of the action. + * @param sourcePreferences The {@link SharedPreferences} used as source. + */ + public IntentBridgedPreferencesRequestedReceiver(String actionName, SharedPreferences sourcePreferences) { + mActionName = actionName; + mSourcePreferences = sourcePreferences; + } + + @Override + public void onReceive(Context context, Intent receivedIntent) { + Intent preferencesIntent = new Intent(mActionName + ".PREFERENCES"); + + for (Map.Entry