WIP Bookmarks 2 support

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Bookmark.java              |  66 
src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java    |   3 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  25 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |   8 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 131 
src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java    |  13 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java   |   2 
src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java   |   6 
src/main/java/eu/siacs/conversations/xml/Namespace.java                  |   1 
src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java        |   9 
10 files changed, 191 insertions(+), 73 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Bookmark.java 🔗

@@ -3,15 +3,23 @@ package eu.siacs.conversations.entities;
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.utils.StringUtils;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.InvalidJid;
 import rocks.xmpp.addr.Jid;
 
@@ -33,11 +41,69 @@ public class Bookmark extends Element implements ListItem {
 		this.account = account;
 	}
 
+	public static Collection<Bookmark> parseFromStorage(Element storage, Account account) {
+		if (storage == null) {
+			return Collections.emptyList();
+		}
+		final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
+		for (final Element item : storage.getChildren()) {
+			if (item.getName().equals("conference")) {
+				final Bookmark bookmark = Bookmark.parse(item, account);
+				if (bookmark != null) {
+					final Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
+					if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
+						bookmark.setBookmarkName(old.getBookmarkName());
+					}
+				}
+			}
+		}
+		return bookmarks.values();
+	}
+
+	public static Collection<Bookmark> parseFromPubsub(Element pubsub, Account account) {
+		if (pubsub == null) {
+			return Collections.emptyList();
+		}
+		final Element items = pubsub.findChild("items");
+		if (items != null && Namespace.BOOKMARK.equals(items.getAttribute("node"))) {
+			final List<Bookmark> bookmarks = new ArrayList<>();
+			for(Element item : items.getChildren()) {
+				if (item.getName().equals("item")) {
+					final Bookmark bookmark = Bookmark.parseFromItem(item, account);
+					if (bookmark != null) {
+						bookmarks.add(bookmark);
+					}
+				}
+			}
+			return bookmarks;
+		}
+		return Collections.emptyList();
+	}
+
 	public static Bookmark parse(Element element, Account account) {
 		Bookmark bookmark = new Bookmark(account);
 		bookmark.setAttributes(element.getAttributes());
 		bookmark.setChildren(element.getChildren());
 		bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid"));
+		if (bookmark.jid == null) {
+			return null;
+		}
+		return bookmark;
+	}
+
+	public static Bookmark parseFromItem(Element item, Account account) {
+		final Element conference = item.findChild("conference", Namespace.BOOKMARK);
+		if (conference == null) {
+			return null;
+		}
+		final Bookmark bookmark = new Bookmark(account);
+		bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
+		if (bookmark.jid == null) {
+			return null;
+		}
+		bookmark.setBookmarkName(conference.getAttribute("name"));
+		bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
+		bookmark.setNick(conference.findChildContent("nick"));
 		return bookmark;
 	}
 

src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java 🔗

@@ -38,7 +38,8 @@ public abstract class AbstractGenerator {
 			"http://jabber.org/protocol/disco#info",
 			"urn:xmpp:avatar:metadata+notify",
 			Namespace.NICK+"+notify",
-			Namespace.BOOKMARKS+"+notify",
+			//Namespace.BOOKMARKS+"+notify",
+			Namespace.BOOKMARK+"+notify",
 			"urn:xmpp:ping",
 			"jabber:iq:version",
 			"http://jabber.org/protocol/chatstates"

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -125,6 +125,10 @@ public class IqGenerator extends AbstractGenerator {
 		return packet;
 	}
 
+	public IqPacket retrieveBookmarks() {
+		return retrieve(Namespace.BOOKMARK, null);
+	}
+
 	public IqPacket publishNick(String nick) {
 		final Element item = new Element("item");
 		item.addChild("nick", Namespace.NICK).setContent(nick);
@@ -146,9 +150,13 @@ public class IqGenerator extends AbstractGenerator {
 		return publish("urn:xmpp:avatar:data", item, options);
 	}
 
-	public IqPacket publishElement(final String namespace,final Element element, final Bundle options) {
+	public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
+		return publishElement(namespace, element, "curent", options);
+	}
+
+	public IqPacket publishElement(final String namespace,final Element element, String id, final Bundle options) {
 		final Element item = new Element("item");
-		item.setAttribute("id","current");
+		item.setAttribute("id",id);
 		item.addChild(element);
 		return publish(namespace, item, options);
 	}
@@ -222,6 +230,19 @@ public class IqGenerator extends AbstractGenerator {
 		return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
 	}
 
+	public Element publishBookmarkItem(final Bookmark bookmark) {
+		final String name = bookmark.getBookmarkName();
+		final String nick = bookmark.getNick();
+		final Element conference = new Element("conference", Namespace.BOOKMARK);
+		if (name != null) {
+			conference.setAttribute("name", name);
+		}
+		if (nick != null) {
+			conference.addChild("nick").setContent(nick);
+		}
+		return conference;
+	}
+
 	public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
 	                               final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
 		final Element item = new Element("item");

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -6,10 +6,12 @@ import android.util.Pair;
 import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -20,6 +22,7 @@ import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
 import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Conversational;
@@ -224,11 +227,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
                 final Element i = items.findChild("item");
                 final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
-                mXmppConnectionService.processBookmarks(account, storage, true);
+                Collection<Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
+                mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
                 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": processing bookmark PEP event");
             } else {
                 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring bookmark PEP event because bookmark conversion was not detected");
             }
+        } else {
+            Log.d(Config.LOGTAG,account.getJid().asBareJid()+" received pubsub notification for node="+node);
         }
     }
 

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -313,9 +313,12 @@ public class XmppConnectionService extends Service {
             mJingleConnectionManager.cancelInTransmission();
             mQuickConversationsService.considerSyncBackground(false);
             fetchRosterFromServer(account);
-            if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
+
+            fetchBookmarks2(account);
+
+            /*if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
                 fetchBookmarks(account);
-            }
+            }*/
             final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
             final boolean catchup = getMessageArchiveService().inCatchup(account);
             if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) {
@@ -1559,7 +1562,8 @@ public class XmppConnectionService extends Service {
             if (response.getType() == IqPacket.TYPE.RESULT) {
                 final Element query1 = response.query();
                 final Element storage = query1.findChild("storage", "storage:bookmarks");
-                processBookmarks(a, storage, false);
+                Collection<Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
+                processBookmarksInitial(a, bookmarks, false);
             } else {
                 Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks");
             }
@@ -1567,59 +1571,63 @@ public class XmppConnectionService extends Service {
         sendIqPacket(account, iqPacket, callback);
     }
 
-    public void processBookmarks(Account account, Element storage, final boolean pep) {
+    public void fetchBookmarks2(final Account account) {
+        final IqPacket retrieve = mIqGenerator.retrieveBookmarks();
+        sendIqPacket(account, retrieve, new OnIqPacketReceived() {
+            @Override
+            public void onIqPacketReceived(final Account account, final IqPacket response) {
+                if (response.getType() == IqPacket.TYPE.RESULT) {
+                    final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
+                    final Collection<Bookmark> bookmarks = Bookmark.parseFromPubsub(pubsub, account);
+                    Log.d(Config.LOGTAG,"bookmarks2 "+pubsub);
+                    Log.d(Config.LOGTAG,"bookmarks2"+ bookmarks);
+                    processBookmarksInitial(account, bookmarks, true);
+                }
+            }
+        });
+    }
+
+    public void processBookmarksInitial(Account account, Collection<Bookmark> bookmarks, final boolean pep) {
         final Set<Jid> previousBookmarks = account.getBookmarkedJids();
-        final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
         final boolean synchronizeWithBookmarks = synchronizeWithBookmarks();
-        if (storage != null) {
-            for (final Element item : storage.getChildren()) {
-                if (item.getName().equals("conference")) {
-                    final Bookmark bookmark = Bookmark.parse(item, account);
-                    Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
-                    if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
-                        bookmark.setBookmarkName(old.getBookmarkName());
-                    }
-                    if (bookmark.getJid() == null) {
-                        continue;
-                    }
-                    previousBookmarks.remove(bookmark.getJid().asBareJid());
-                    Conversation conversation = find(bookmark);
-                    if (conversation != null) {
-                        if (conversation.getMode() != Conversation.MODE_MULTI) {
-                            continue;
-                        }
-                        bookmark.setConversation(conversation);
-                        if (pep && synchronizeWithBookmarks && !bookmark.autojoin()) {
-                            Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving conference ("+conversation.getJid()+") after receiving pep");
-                            archiveConversation(conversation, false);
-                        }
-                    } else if (synchronizeWithBookmarks && bookmark.autojoin()) {
-                        conversation = findOrCreateConversation(account, bookmark.getFullJid(), true, true, false);
-                        bookmark.setConversation(conversation);
-                    }
+        for (Bookmark bookmark : bookmarks) {
+            previousBookmarks.remove(bookmark.getJid().asBareJid());
+            Conversation conversation = find(bookmark);
+            if (conversation != null) {
+                if (conversation.getMode() != Conversation.MODE_MULTI) {
+                    continue;
+                }
+                bookmark.setConversation(conversation);
+                if (pep && synchronizeWithBookmarks && !bookmark.autojoin()) {
+                    Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving conference ("+conversation.getJid()+") after receiving pep");
+                    archiveConversation(conversation, false);
                 }
+            } else if (synchronizeWithBookmarks && bookmark.autojoin()) {
+                conversation = findOrCreateConversation(account, bookmark.getFullJid(), true, true, false);
+                bookmark.setConversation(conversation);
             }
-            if (pep && synchronizeWithBookmarks) {
-                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + previousBookmarks.size() + " bookmarks have been removed");
-                for (Jid jid : previousBookmarks) {
-                    final Conversation conversation = find(account, jid);
-                    if (conversation != null && conversation.getMucOptions().getError() == MucOptions.Error.DESTROYED) {
-                        Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving destroyed conference ("+conversation.getJid()+") after receiving pep");
-                        archiveConversation(conversation, false);
-                    }
+        }
+        if (pep && synchronizeWithBookmarks) {
+            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + previousBookmarks.size() + " bookmarks have been removed");
+            for (Jid jid : previousBookmarks) {
+                final Conversation conversation = find(account, jid);
+                if (conversation != null && conversation.getMucOptions().getError() == MucOptions.Error.DESTROYED) {
+                    Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving destroyed conference ("+conversation.getJid()+") after receiving pep");
+                    archiveConversation(conversation, false);
                 }
             }
         }
-        account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks.values()));
+        account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks));
     }
 
-    public void pushBookmarks(Account account) {
+    public void createBookmark(final Account account, final Bookmark bookmark) {
+        final Element item = mIqGenerator.publishBookmarkItem(bookmark);
+        pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARK, item, bookmark.getJid().asBareJid().toEscapedString(), PublishOptions.persistentWhitelistAccessMaxItems());
+    }
+
+    public void deleteBookmark(final Account account, final Bookmark bookmark) {
         final XmppConnection connection = account.getXmppConnection();
-        if (connection != null && connection.getFeatures().bookmarksConversion()) {
-            pushBookmarksPep(account);
-        } else {
-            pushBookmarksPrivateXml(account);
-        }
+
     }
 
     private void pushBookmarksPrivateXml(Account account) {
@@ -1643,14 +1651,18 @@ public class XmppConnectionService extends Service {
 
     }
 
-
     private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options) {
-        pushNodeAndEnforcePublishOptions(account, node, element, options, true);
+        pushNodeAndEnforcePublishOptions(account, node, element, null, options, true);
+
+    }
+
+    private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options) {
+        pushNodeAndEnforcePublishOptions(account, node, element, id, options, true);
 
     }
 
-	private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options, final boolean retry) {
-        final IqPacket packet = mIqGenerator.publishElement(node, element, options);
+	private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options, final boolean retry) {
+        final IqPacket packet = mIqGenerator.publishElement(node, element, id, options);
         sendIqPacket(account, packet, (a, response) -> {
             if (response.getType() == IqPacket.TYPE.RESULT) {
                 return;
@@ -1659,7 +1671,7 @@ public class XmppConnectionService extends Service {
                 pushNodeConfiguration(account, node, options, new OnConfigurationPushed() {
                     @Override
                     public void onPushSucceeded() {
-                        pushNodeAndEnforcePublishOptions(account, node, element, options, false);
+                        pushNodeAndEnforcePublishOptions(account, node, element, id, options, false);
                     }
 
                     @Override
@@ -2043,10 +2055,10 @@ public class XmppConnectionService extends Service {
 							Account account = bookmark.getAccount();
 							bookmark.setConversation(null);
 							account.getBookmarks().remove(bookmark);
-							pushBookmarks(account);
+							deleteBookmark(account, bookmark);
 						} else if (bookmark.autojoin()) {
 							bookmark.setAutojoin(false);
-							pushBookmarks(bookmark.getAccount());
+							createBookmark(bookmark.getAccount(), bookmark);
 						}
 					}
 				}
@@ -2767,10 +2779,11 @@ public class XmppConnectionService extends Service {
 		if (conversation.getMode() == Conversation.MODE_MULTI) {
 			conversation.getMucOptions().setPassword(password);
 			if (conversation.getBookmark() != null) {
+			    final Bookmark bookmark = conversation.getBookmark();
 				if (synchronizeWithBookmarks()) {
-					conversation.getBookmark().setAutojoin(true);
+					bookmark.setAutojoin(true);
 				}
-				pushBookmarks(conversation.getAccount());
+				createBookmark(conversation.getAccount(), bookmark);
 			}
 			updateConversation(conversation);
 			joinMuc(conversation);
@@ -2824,7 +2837,7 @@ public class XmppConnectionService extends Service {
             }
             Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persist nick '" + full.getResource() + "' into bookmark for " + conversation.getJid().asBareJid());
             bookmark.setNick(full.getResource());
-            pushBookmarks(bookmark.getAccount());
+            createBookmark(bookmark.getAccount(), bookmark);
         }
 	}
 
@@ -2859,7 +2872,7 @@ public class XmppConnectionService extends Service {
 				Bookmark bookmark = conversation.getBookmark();
 				if (bookmark != null) {
 					bookmark.setNick(nick);
-					pushBookmarks(bookmark.getAccount());
+					createBookmark(bookmark.getAccount(), bookmark);
 				}
 				joinMuc(conversation);
 			}
@@ -3027,7 +3040,7 @@ public class XmppConnectionService extends Service {
 
                     if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) {
                         if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) {
-                            pushBookmarks(account);
+                            createBookmark(account, bookmark);
                         }
                     }
 
@@ -4442,7 +4455,7 @@ public class XmppConnectionService extends Service {
 		}
 		bookmark.setAutojoin(getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin)));
 		account.getBookmarks().add(bookmark);
-		pushBookmarks(account);
+		createBookmark(account, bookmark);
 		bookmark.setConversation(conversation);
 	}
 

src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java 🔗

@@ -223,16 +223,17 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
         final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin);
         final Account account = xmppConnectionService.findAccountByJid(jid);
         final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true);
-        if (conversation.getBookmark() != null) {
-            if (!conversation.getBookmark().autojoin() && syncAutoJoin) {
+        Bookmark bookmark = conversation.getBookmark();
+        if (bookmark != null) {
+            if (!bookmark.autojoin() && syncAutojoin) {
                 conversation.getBookmark().setAutojoin(true);
-                xmppConnectionService.pushBookmarks(account);
+                xmppConnectionService.createBookmark(account, bookmark);
             }
         } else {
-            final Bookmark bookmark = new Bookmark(account, conversation.getJid().asBareJid());
-            bookmark.setAutojoin(syncAutoJoin);
+            bookmark = new Bookmark(account, conversation.getJid().asBareJid());
+            bookmark.setAutojoin(syncAutojoin);
             account.getBookmarks().add(bookmark);
-            xmppConnectionService.pushBookmarks(account);
+            xmppConnectionService.createBookmark(account, bookmark);
         }
         switchToConversation(conversation);
     }

src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java 🔗

@@ -390,7 +390,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         Bookmark bookmark = mConversation.getBookmark();
         account.getBookmarks().remove(bookmark);
         bookmark.setConversation(null);
-        xmppConnectionService.pushBookmarks(account);
+        xmppConnectionService.deleteBookmark(account, bookmark);
         updateView();
     }
 

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java 🔗

@@ -431,7 +431,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		bookmark.setConversation(conversation);
 		if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
 			bookmark.setAutojoin(true);
-			xmppConnectionService.pushBookmarks(bookmark.getAccount());
+			xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
 		}
 		SoftKeyboardUtils.hideSoftKeyboard(this);
 		switchToConversation(conversation);
@@ -480,7 +480,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 			bookmark.setConversation(null);
 			Account account = bookmark.getAccount();
 			account.getBookmarks().remove(bookmark);
-			xmppConnectionService.pushBookmarks(account);
+			xmppConnectionService.deleteBookmark(account, bookmark);
 			filter(mSearchEditText.getText().toString());
 		});
 		builder.create().show();
@@ -1042,7 +1042,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 					bookmark.setNick(nick);
 				}
 				account.getBookmarks().add(bookmark);
-				xmppConnectionService.pushBookmarks(account);
+				xmppConnectionService.createBookmark(account, bookmark);
 				final Conversation conversation = xmppConnectionService
 						.findOrCreateConversation(account, conferenceJid, true, true, true);
 				bookmark.setConversation(conversation);

src/main/java/eu/siacs/conversations/xml/Namespace.java 🔗

@@ -33,4 +33,5 @@ public final class Namespace {
 	public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
 	public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
 	public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
+	public static final String BOOKMARK = "urn:xmpp:bookmarks:0";
 }

src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java 🔗

@@ -25,6 +25,15 @@ public class PublishOptions {
         return options;
     }
 
+    public static Bundle persistentWhitelistAccessMaxItems() {
+        final Bundle options = new Bundle();
+        options.putString("pubsub#persist_items","true");
+        options.putString("pubsub#access_model", "whitelist");
+        options.putString("pubsub#send_last_published_item","never");
+        options.putString("pubsub#max_items","128"); //YOLO!
+        return options;
+    }
+
     public static boolean preconditionNotMet(IqPacket response) {
         final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null;
         return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR);