PrivacyListManager.java

  1. /**
  2.  *
  3.  * Copyright 2006-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.privacy;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import java.util.WeakHashMap;
  23. import java.util.concurrent.CopyOnWriteArraySet;

  24. import org.jivesoftware.smack.ConnectionCreationListener;
  25. import org.jivesoftware.smack.ConnectionListener;
  26. import org.jivesoftware.smack.Manager;
  27. import org.jivesoftware.smack.SmackException.NoResponseException;
  28. import org.jivesoftware.smack.SmackException.NotConnectedException;
  29. import org.jivesoftware.smack.StanzaListener;
  30. import org.jivesoftware.smack.XMPPConnection;
  31. import org.jivesoftware.smack.XMPPConnectionRegistry;
  32. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  33. import org.jivesoftware.smack.filter.AndFilter;
  34. import org.jivesoftware.smack.filter.IQResultReplyFilter;
  35. import org.jivesoftware.smack.filter.IQTypeFilter;
  36. import org.jivesoftware.smack.filter.StanzaFilter;
  37. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  38. import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
  39. import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
  40. import org.jivesoftware.smack.packet.IQ;
  41. import org.jivesoftware.smack.packet.Stanza;
  42. import org.jivesoftware.smack.util.StringUtils;

  43. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  44. import org.jivesoftware.smackx.privacy.filter.SetActiveListFilter;
  45. import org.jivesoftware.smackx.privacy.filter.SetDefaultListFilter;
  46. import org.jivesoftware.smackx.privacy.packet.Privacy;
  47. import org.jivesoftware.smackx.privacy.packet.PrivacyItem;

  48. /**
  49.  * A PrivacyListManager is used by XMPP clients to block or allow communications from other
  50.  * users. Use the manager to:
  51.  * <ul>
  52.  *      <li>Retrieve privacy lists.
  53.  *      <li>Add, remove, and edit privacy lists.
  54.  *      <li>Set, change, or decline active lists.
  55.  *      <li>Set, change, or decline the default list (i.e., the list that is active by default).
  56.  * </ul>
  57.  * Privacy Items can handle different kind of permission communications based on JID, group,
  58.  * subscription type or globally (see {@link PrivacyItem}).
  59.  *
  60.  * @author Francisco Vives
  61.  * @see <a href="http://xmpp.org/extensions/xep-0016.html">XEP-16: Privacy Lists</a>
  62.  */
  63. public final class PrivacyListManager extends Manager {
  64.     public static final String NAMESPACE = Privacy.NAMESPACE;

  65.     public static final StanzaFilter PRIVACY_FILTER = new StanzaTypeFilter(Privacy.class);

  66.     private static final StanzaFilter PRIVACY_RESULT = new AndFilter(IQTypeFilter.RESULT, PRIVACY_FILTER);

  67.     // Keep the list of instances of this class.
  68.     private static final Map<XMPPConnection, PrivacyListManager> INSTANCES = new WeakHashMap<>();

  69.     private final Set<PrivacyListListener> listeners = new CopyOnWriteArraySet<>();

  70.     static {
  71.         // Create a new PrivacyListManager on every established connection.
  72.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  73.             @Override
  74.             public void connectionCreated(XMPPConnection connection) {
  75.                 getInstanceFor(connection);
  76.             }
  77.         });
  78.     }

  79.     // TODO implement: private final Map<String, PrivacyList> cachedPrivacyLists = new HashMap<>();
  80.     private volatile String cachedActiveListName;
  81.     private volatile String cachedDefaultListName;

  82.     /**
  83.      * Creates a new privacy manager to maintain the communication privacy. Note: no
  84.      * information is sent to or received from the server until you attempt to
  85.      * get or set the privacy communication.<p>
  86.      *
  87.      * @param connection the XMPP connection.
  88.      */
  89.     private PrivacyListManager(XMPPConnection connection) {
  90.         super(connection);

  91.         connection.registerIQRequestHandler(new AbstractIqRequestHandler(Privacy.ELEMENT, Privacy.NAMESPACE,
  92.                         IQ.Type.set, Mode.sync) {
  93.             @Override
  94.             public IQ handleIQRequest(IQ iqRequest) {
  95.                 Privacy privacy = (Privacy) iqRequest;

  96.                 // Notifies the event to the listeners.
  97.                 for (PrivacyListListener listener : listeners) {
  98.                     // Notifies the created or updated privacy lists
  99.                     for (Map.Entry<String, List<PrivacyItem>> entry : privacy.getItemLists().entrySet()) {
  100.                         String listName = entry.getKey();
  101.                         List<PrivacyItem> items = entry.getValue();
  102.                         if (items.isEmpty()) {
  103.                             listener.updatedPrivacyList(listName);
  104.                         }
  105.                         else {
  106.                             listener.setPrivacyList(listName, items);
  107.                         }
  108.                     }
  109.                 }

  110.                 return IQ.createResultIQ(privacy);
  111.             }
  112.         });

  113.         // cached(Active|Default)ListName handling
  114.         connection.addStanzaSendingListener(new StanzaListener() {
  115.             @Override
  116.             public void processStanza(Stanza packet) throws NotConnectedException {
  117.                 XMPPConnection connection = connection();
  118.                 Privacy privacy = (Privacy) packet;
  119.                 StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
  120.                 final String activeListName = privacy.getActiveName();
  121.                 final boolean declinceActiveList = privacy.isDeclineActiveList();
  122.                 connection.addOneTimeSyncCallback(new StanzaListener() {
  123.                     @Override
  124.                     public void processStanza(Stanza packet) throws NotConnectedException {
  125.                             if (declinceActiveList) {
  126.                                 cachedActiveListName = null;
  127.                             }
  128.                             else {
  129.                                 cachedActiveListName = activeListName;
  130.                             }
  131.                             return;
  132.                     }
  133.                 }, iqResultReplyFilter);
  134.             }
  135.         }, SetActiveListFilter.INSTANCE);
  136.         connection.addStanzaSendingListener(new StanzaListener() {
  137.             @Override
  138.             public void processStanza(Stanza packet) throws NotConnectedException {
  139.                 XMPPConnection connection = connection();
  140.                 Privacy privacy = (Privacy) packet;
  141.                 StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
  142.                 final String defaultListName = privacy.getDefaultName();
  143.                 final boolean declinceDefaultList = privacy.isDeclineDefaultList();
  144.                 connection.addOneTimeSyncCallback(new StanzaListener() {
  145.                     @Override
  146.                     public void processStanza(Stanza packet) throws NotConnectedException {
  147.                             if (declinceDefaultList) {
  148.                                 cachedDefaultListName = null;
  149.                             }
  150.                             else {
  151.                                 cachedDefaultListName = defaultListName;
  152.                             }
  153.                             return;
  154.                     }
  155.                 }, iqResultReplyFilter);
  156.             }
  157.         }, SetDefaultListFilter.INSTANCE);
  158.         connection.addSyncStanzaListener(new StanzaListener() {
  159.             @Override
  160.             public void processStanza(Stanza packet) throws NotConnectedException {
  161.                 Privacy privacy = (Privacy) packet;
  162.                 // If a privacy IQ result stanza has an active or default list name set, then we use that
  163.                 // as cached list name.
  164.                 String activeList = privacy.getActiveName();
  165.                 if (activeList != null) {
  166.                     cachedActiveListName = activeList;
  167.                 }
  168.                 String defaultList = privacy.getDefaultName();
  169.                 if (defaultList != null) {
  170.                     cachedDefaultListName = defaultList;
  171.                 }
  172.             }
  173.         }, PRIVACY_RESULT);
  174.         connection.addConnectionListener(new ConnectionListener() {
  175.             @Override
  176.             public void authenticated(XMPPConnection connection, boolean resumed) {
  177.                 // No need to reset the cache if the connection got resumed.
  178.                 if (resumed) {
  179.                     return;
  180.                 }
  181.                 cachedActiveListName = cachedDefaultListName = null;
  182.             }
  183.         });

  184.         // XEP-0016 ยง 3.
  185.         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
  186.     }

  187.     /**
  188.      * Returns the PrivacyListManager instance associated with a given XMPPConnection.
  189.      *
  190.      * @param connection the connection used to look for the proper PrivacyListManager.
  191.      * @return the PrivacyListManager associated with a given XMPPConnection.
  192.      */
  193.     public static synchronized PrivacyListManager getInstanceFor(XMPPConnection connection) {
  194.         PrivacyListManager plm = INSTANCES.get(connection);
  195.         if (plm == null) {
  196.             plm = new PrivacyListManager(connection);
  197.             // Register the new instance and associate it with the connection
  198.             INSTANCES.put(connection, plm);
  199.         }
  200.         return plm;
  201.     }

  202.     /**
  203.      * Send the {@link Privacy} stanza to the server in order to know some privacy content and then
  204.      * waits for the answer.
  205.      *
  206.      * @param requestPrivacy is the {@link Privacy} stanza configured properly whose XML
  207.      *      will be sent to the server.
  208.      * @return a new {@link Privacy} with the data received from the server.
  209.      * @throws XMPPErrorException if there was an XMPP error returned.
  210.      * @throws NoResponseException if there was no response from the remote entity.
  211.      * @throws NotConnectedException if the XMPP connection is not connected.
  212.      * @throws InterruptedException if the calling thread was interrupted.
  213.      */
  214.     private Privacy getRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  215.         // The request is a get iq type
  216.         requestPrivacy.setType(Privacy.Type.get);

  217.         return connection().sendIqRequestAndWaitForResponse(requestPrivacy);
  218.     }

  219.     /**
  220.      * Send the {@link Privacy} stanza to the server in order to modify the server privacy and waits
  221.      * for the answer.
  222.      *
  223.      * @param requestPrivacy is the {@link Privacy} stanza configured properly whose xml will be
  224.      *        sent to the server.
  225.      * @return a new {@link Privacy} with the data received from the server.
  226.      * @throws XMPPErrorException if there was an XMPP error returned.
  227.      * @throws NoResponseException if there was no response from the remote entity.
  228.      * @throws NotConnectedException if the XMPP connection is not connected.
  229.      * @throws InterruptedException if the calling thread was interrupted.
  230.      */
  231.     private Stanza setRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  232.         // The request is a get iq type
  233.         requestPrivacy.setType(Privacy.Type.set);

  234.         return connection().sendIqRequestAndWaitForResponse(requestPrivacy);
  235.     }

  236.     /**
  237.      * Answer a privacy containing the list structure without {@link PrivacyItem}.
  238.      *
  239.      * @return a Privacy with the list names.
  240.      * @throws XMPPErrorException if there was an XMPP error returned.
  241.      * @throws NoResponseException if there was no response from the remote entity.
  242.      * @throws NotConnectedException if the XMPP connection is not connected.
  243.      * @throws InterruptedException if the calling thread was interrupted.
  244.      */
  245.     private Privacy getPrivacyWithListNames() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  246.         // The request of the list is an empty privacy message
  247.         Privacy request = new Privacy();

  248.         // Send the package to the server and get the answer
  249.         return getRequest(request);
  250.     }

  251.     /**
  252.      * Answer the active privacy list. Returns <code>null</code> if there is no active list.
  253.      *
  254.      * @return the privacy list of the active list.
  255.      * @throws XMPPErrorException if there was an XMPP error returned.
  256.      * @throws NoResponseException if there was no response from the remote entity.
  257.      * @throws NotConnectedException if the XMPP connection is not connected.
  258.      * @throws InterruptedException if the calling thread was interrupted.
  259.      */
  260.     public PrivacyList getActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  261.         Privacy privacyAnswer = this.getPrivacyWithListNames();
  262.         String listName = privacyAnswer.getActiveName();
  263.         if (StringUtils.isNullOrEmpty(listName)) {
  264.             return null;
  265.         }
  266.         boolean isDefaultAndActive = listName != null && listName.equals(privacyAnswer.getDefaultName());
  267.         return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName));
  268.     }

  269.     /**
  270.      * Get the name of the active list.
  271.      *
  272.      * @return the name of the active list or null if there is none set.
  273.      * @throws NoResponseException if there was no response from the remote entity.
  274.      * @throws XMPPErrorException if there was an XMPP error returned.
  275.      * @throws NotConnectedException if the XMPP connection is not connected.
  276.      * @throws InterruptedException if the calling thread was interrupted.
  277.      * @since 4.1
  278.      */
  279.     public String getActiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  280.         if (cachedActiveListName != null) {
  281.             return cachedActiveListName;
  282.         }
  283.         return getPrivacyWithListNames().getActiveName();
  284.     }

  285.     /**
  286.      * Answer the default privacy list. Returns <code>null</code> if there is no default list.
  287.      *
  288.      * @return the privacy list of the default list.
  289.      * @throws XMPPErrorException if there was an XMPP error returned.
  290.      * @throws NoResponseException if there was no response from the remote entity.
  291.      * @throws NotConnectedException if the XMPP connection is not connected.
  292.      * @throws InterruptedException if the calling thread was interrupted.
  293.      */
  294.     public PrivacyList getDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  295.         Privacy privacyAnswer = this.getPrivacyWithListNames();
  296.         String listName = privacyAnswer.getDefaultName();
  297.         if (StringUtils.isNullOrEmpty(listName)) {
  298.             return null;
  299.         }
  300.         boolean isDefaultAndActive = listName.equals(privacyAnswer.getActiveName());
  301.         return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName));
  302.     }

  303.     /**
  304.      * Get the name of the default list.
  305.      *
  306.      * @return the name of the default list or null if there is none set.
  307.      * @throws NoResponseException if there was no response from the remote entity.
  308.      * @throws XMPPErrorException if there was an XMPP error returned.
  309.      * @throws NotConnectedException if the XMPP connection is not connected.
  310.      * @throws InterruptedException if the calling thread was interrupted.
  311.      * @since 4.1
  312.      */
  313.     public String getDefaultListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  314.         if (cachedDefaultListName != null) {
  315.             return cachedDefaultListName;
  316.         }
  317.         return getPrivacyWithListNames().getDefaultName();
  318.     }

  319.     /**
  320.      * Returns the name of the effective privacy list.
  321.      * <p>
  322.      * The effective privacy list is the one that is currently enforced on the connection. It's either the active
  323.      * privacy list, or, if the active privacy list is not set, the default privacy list.
  324.      * </p>
  325.      *
  326.      * @return the name of the effective privacy list or null if there is none set.
  327.      * @throws NoResponseException if there was no response from the remote entity.
  328.      * @throws XMPPErrorException if there was an XMPP error returned.
  329.      * @throws NotConnectedException if the XMPP connection is not connected.
  330.      * @throws InterruptedException if the calling thread was interrupted.
  331.      * @since 4.1
  332.      */
  333.     public String getEffectiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  334.         String activeListName = getActiveListName();
  335.         if (activeListName != null) {
  336.             return activeListName;
  337.         }
  338.         return getDefaultListName();
  339.     }

  340.     /**
  341.      * Answer the privacy list items under listName with the allowed and blocked permissions.
  342.      *
  343.      * @param listName the name of the list to get the allowed and blocked permissions.
  344.      * @return a list of privacy items under the list listName.
  345.      * @throws XMPPErrorException if there was an XMPP error returned.
  346.      * @throws NoResponseException if there was no response from the remote entity.
  347.      * @throws NotConnectedException if the XMPP connection is not connected.
  348.      * @throws InterruptedException if the calling thread was interrupted.
  349.      */
  350.     private List<PrivacyItem> getPrivacyListItems(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  351.         assert StringUtils.isNotEmpty(listName);
  352.         // The request of the list is an privacy message with an empty list
  353.         Privacy request = new Privacy();
  354.         request.setPrivacyList(listName, new ArrayList<PrivacyItem>());

  355.         // Send the package to the server and get the answer
  356.         Privacy privacyAnswer = getRequest(request);

  357.         return privacyAnswer.getPrivacyList(listName);
  358.     }

  359.     /**
  360.      * Answer the privacy list items under listName with the allowed and blocked permissions.
  361.      *
  362.      * @param listName the name of the list to get the allowed and blocked permissions.
  363.      * @return a privacy list under the list listName.
  364.      * @throws XMPPErrorException if there was an XMPP error returned.
  365.      * @throws NoResponseException if there was no response from the remote entity.
  366.      * @throws NotConnectedException if the XMPP connection is not connected.
  367.      * @throws InterruptedException if the calling thread was interrupted.
  368.      */
  369.     public PrivacyList getPrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  370.         listName = StringUtils.requireNotNullNorEmpty(listName, "List name must not be null");
  371.         return new PrivacyList(false, false, listName, getPrivacyListItems(listName));
  372.     }

  373.     /**
  374.      * Answer every privacy list with the allowed and blocked permissions.
  375.      *
  376.      * @return an array of privacy lists.
  377.      * @throws XMPPErrorException if there was an XMPP error returned.
  378.      * @throws NoResponseException if there was no response from the remote entity.
  379.      * @throws NotConnectedException if the XMPP connection is not connected.
  380.      * @throws InterruptedException if the calling thread was interrupted.
  381.      */
  382.     public List<PrivacyList> getPrivacyLists() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  383.         Privacy privacyAnswer = getPrivacyWithListNames();
  384.         Set<String> names = privacyAnswer.getPrivacyListNames();
  385.         List<PrivacyList> lists = new ArrayList<>(names.size());
  386.         for (String listName : names) {
  387.             boolean isActiveList = listName.equals(privacyAnswer.getActiveName());
  388.             boolean isDefaultList = listName.equals(privacyAnswer.getDefaultName());
  389.             lists.add(new PrivacyList(isActiveList, isDefaultList, listName,
  390.                             getPrivacyListItems(listName)));
  391.         }
  392.         return lists;
  393.     }

  394.     /**
  395.      * Set or change the active list to listName.
  396.      *
  397.      * @param listName the list name to set as the active one.
  398.      * @throws XMPPErrorException if there was an XMPP error returned.
  399.      * @throws NoResponseException if there was no response from the remote entity.
  400.      * @throws NotConnectedException if the XMPP connection is not connected.
  401.      * @throws InterruptedException if the calling thread was interrupted.
  402.      */
  403.     public void setActiveListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  404.         // The request of the list is an privacy message with an empty list
  405.         Privacy request = new Privacy();
  406.         request.setActiveName(listName);

  407.         // Send the package to the server
  408.         setRequest(request);
  409.     }

  410.     /**
  411.      * Client declines the use of active lists.
  412.      * @throws XMPPErrorException if there was an XMPP error returned.
  413.      * @throws NoResponseException if there was no response from the remote entity.
  414.      * @throws NotConnectedException if the XMPP connection is not connected.
  415.      * @throws InterruptedException if the calling thread was interrupted.
  416.      */
  417.     public void declineActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  418.         // The request of the list is an privacy message with an empty list
  419.         Privacy request = new Privacy();
  420.         request.setDeclineActiveList(true);

  421.         // Send the package to the server
  422.         setRequest(request);
  423.     }

  424.     /**
  425.      * Set or change the default list to listName.
  426.      *
  427.      * @param listName the list name to set as the default one.
  428.      * @throws XMPPErrorException if there was an XMPP error returned.
  429.      * @throws NoResponseException if there was no response from the remote entity.
  430.      * @throws NotConnectedException if the XMPP connection is not connected.
  431.      * @throws InterruptedException if the calling thread was interrupted.
  432.      */
  433.     public void setDefaultListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  434.         // The request of the list is an privacy message with an empty list
  435.         Privacy request = new Privacy();
  436.         request.setDefaultName(listName);

  437.         // Send the package to the server
  438.         setRequest(request);
  439.     }

  440.     /**
  441.      * Client declines the use of default lists.
  442.      * @throws XMPPErrorException if there was an XMPP error returned.
  443.      * @throws NoResponseException if there was no response from the remote entity.
  444.      * @throws NotConnectedException if the XMPP connection is not connected.
  445.      * @throws InterruptedException if the calling thread was interrupted.
  446.      */
  447.     public void declineDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  448.         // The request of the list is an privacy message with an empty list
  449.         Privacy request = new Privacy();
  450.         request.setDeclineDefaultList(true);

  451.         // Send the package to the server
  452.         setRequest(request);
  453.     }

  454.     /**
  455.      * The client has created a new list. It send the new one to the server.
  456.      *
  457.      * @param listName the list that has changed its content.
  458.      * @param privacyItems a List with every privacy item in the list.
  459.      * @throws XMPPErrorException if there was an XMPP error returned.
  460.      * @throws NoResponseException if there was no response from the remote entity.
  461.      * @throws NotConnectedException if the XMPP connection is not connected.
  462.      * @throws InterruptedException if the calling thread was interrupted.
  463.      */
  464.     public void createPrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  465.         updatePrivacyList(listName, privacyItems);
  466.     }

  467.     /**
  468.      * The client has edited an existing list. It updates the server content with the resulting
  469.      * list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the
  470.      * list (not the "delta").
  471.      *
  472.      * @param listName the list that has changed its content.
  473.      * @param privacyItems a List with every privacy item in the list.
  474.      * @throws XMPPErrorException if there was an XMPP error returned.
  475.      * @throws NoResponseException if there was no response from the remote entity.
  476.      * @throws NotConnectedException if the XMPP connection is not connected.
  477.      * @throws InterruptedException if the calling thread was interrupted.
  478.      */
  479.     public void updatePrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  480.         // Build the privacy package to add or update the new list
  481.         Privacy request = new Privacy();
  482.         request.setPrivacyList(listName, privacyItems);

  483.         // Send the package to the server
  484.         setRequest(request);
  485.     }

  486.     /**
  487.      * Remove a privacy list.
  488.      *
  489.      * @param listName the list that has changed its content.
  490.      * @throws XMPPErrorException if there was an XMPP error returned.
  491.      * @throws NoResponseException if there was no response from the remote entity.
  492.      * @throws NotConnectedException if the XMPP connection is not connected.
  493.      * @throws InterruptedException if the calling thread was interrupted.
  494.      */
  495.     public void deletePrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  496.         // The request of the list is an privacy message with an empty list
  497.         Privacy request = new Privacy();
  498.         request.setPrivacyList(listName, new ArrayList<PrivacyItem>());

  499.         // Send the package to the server
  500.         setRequest(request);
  501.     }

  502.     /**
  503.      * Adds a privacy list listener that will be notified of any new update in the user
  504.      * privacy communication.
  505.      *
  506.      * @param listener a privacy list listener.
  507.      * @return true, if the listener was not already added.
  508.      */
  509.     public boolean addListener(PrivacyListListener listener) {
  510.         return listeners.add(listener);
  511.     }

  512.     /**
  513.      * Removes the privacy list listener.
  514.      *
  515.      * @param listener TODO javadoc me please
  516.      * @return true, if the listener was removed.
  517.      */
  518.     public boolean removeListener(PrivacyListListener listener) {
  519.         return listeners.remove(listener);
  520.     }

  521.     /**
  522.      * Check if the user's server supports privacy lists.
  523.      *
  524.      * @return true, if the server supports privacy lists, false otherwise.
  525.      * @throws XMPPErrorException if there was an XMPP error returned.
  526.      * @throws NoResponseException if there was no response from the remote entity.
  527.      * @throws NotConnectedException if the XMPP connection is not connected.
  528.      * @throws InterruptedException if the calling thread was interrupted.
  529.      */
  530.     public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  531.         return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE);
  532.     }
  533. }