/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data.roster; import com.xabber.android.data.Application; import com.xabber.android.data.NetworkException; import com.xabber.android.data.OnLoadListener; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.OnAccountDisabledListener; import com.xabber.android.data.account.StatusMode; import com.xabber.android.data.connection.ConnectionItem; import com.xabber.android.data.connection.ConnectionManager; import com.xabber.android.data.connection.OnDisconnectListener; import com.xabber.android.data.connection.OnPacketListener; import com.xabber.android.data.entity.NestedMap; import com.xabber.android.data.extension.archive.OnArchiveModificationsReceivedListener; import com.xabber.android.data.notification.EntityNotificationProvider; import com.xabber.android.data.notification.NotificationManager; import com.xabber.androiddev.R; import com.xabber.xmpp.address.Jid; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence.Type; import org.jivesoftware.smack.packet.RosterPacket; import org.jivesoftware.smack.packet.RosterPacket.ItemType; import org.jivesoftware.smack.util.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; /** * Process contact's presence information. * * @author alexander.ivanov */ public class PresenceManager implements OnArchiveModificationsReceivedListener, OnPacketListener, OnLoadListener, OnAccountDisabledListener, OnDisconnectListener { private final EntityNotificationProvider<SubscriptionRequest> subscriptionRequestProvider; /** * List of account with requested subscriptions for auto accept incoming * subscription request. */ private final HashMap<String, HashSet<String>> requestedSubscriptions; /** * Presence container for bare address in account. */ private final NestedMap<ResourceContainer> presenceContainers; /** * Account ready to send / update its presence information. */ private final ArrayList<String> readyAccounts; private final static PresenceManager instance; static { instance = new PresenceManager(); Application.getInstance().addManager(instance); } public static PresenceManager getInstance() { return instance; } private PresenceManager() { subscriptionRequestProvider = new EntityNotificationProvider<SubscriptionRequest>( R.drawable.ic_stat_ic_add_circle); requestedSubscriptions = new HashMap<String, HashSet<String>>(); presenceContainers = new NestedMap<ResourceContainer>(); readyAccounts = new ArrayList<String>(); } @Override public void onLoad() { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { onLoaded(); } }); } private void onLoaded() { NotificationManager.getInstance().registerNotificationProvider( subscriptionRequestProvider); } /** * @param account * @param user * @return <code>null</code> can be returned. */ public SubscriptionRequest getSubscriptionRequest(String account, String user) { return subscriptionRequestProvider.get(account, user); } /** * Requests subscription to the contact. * * @param account * @param bareAddress * @throws NetworkException */ public void requestSubscription(String account, String bareAddress) throws NetworkException { Presence packet = new Presence(Presence.Type.subscribe); packet.setTo(bareAddress); ConnectionManager.getInstance().sendPacket(account, packet); HashSet<String> set = requestedSubscriptions.get(account); if (set == null) { set = new HashSet<String>(); requestedSubscriptions.put(account, set); } set.add(bareAddress); } private void removeRequestedSubscription(String account, String bareAddress) { HashSet<String> set = requestedSubscriptions.get(account); if (set != null) set.remove(bareAddress); } /** * Accepts subscription request from the entity (share own presence). * * @param account * @param bareAddress * @throws NetworkException */ public void acceptSubscription(String account, String bareAddress) throws NetworkException { Presence packet = new Presence(Presence.Type.subscribed); packet.setTo(bareAddress); ConnectionManager.getInstance().sendPacket(account, packet); subscriptionRequestProvider.remove(account, bareAddress); removeRequestedSubscription(account, bareAddress); } /** * Discards subscription request from the entity (deny own presence * sharing). * * @param account * @param bareAddress * @throws NetworkException */ public void discardSubscription(String account, String bareAddress) throws NetworkException { Presence packet = new Presence(Presence.Type.unsubscribed); packet.setTo(bareAddress); ConnectionManager.getInstance().sendPacket(account, packet); subscriptionRequestProvider.remove(account, bareAddress); removeRequestedSubscription(account, bareAddress); } public boolean hasSubscriptionRequest(String account, String bareAddress) { return getSubscriptionRequest(account, bareAddress) != null; } /** * @param account * @param bareAddress * @return Best resource item for specified user. <code>null</code> if there * is no such user or user has no available resource. */ public ResourceItem getResourceItem(String account, String bareAddress) { ResourceContainer resourceContainer = presenceContainers.get(account, bareAddress); if (resourceContainer == null) return null; return resourceContainer.getBest(); } /** * @return Collection with available resources. */ public Collection<ResourceItem> getResourceItems(String account, String bareAddress) { ResourceContainer container = presenceContainers.get(account, bareAddress); if (container == null) return Collections.emptyList(); return container.getResourceItems(); } public StatusMode getStatusMode(String account, String bareAddress) { ResourceItem resourceItem = getResourceItem(account, bareAddress); if (resourceItem == null) return StatusMode.unavailable; return resourceItem.getStatusMode(); } public String getStatusText(String account, String bareAddress) { ResourceItem resourceItem = getResourceItem(account, bareAddress); if (resourceItem == null) return ""; return resourceItem.getStatusText(); } @Override public void onPacket(ConnectionItem connection, String bareAddress, Packet packet) { if (!(connection instanceof AccountItem)) return; String account = ((AccountItem) connection).getAccount(); if (packet instanceof Presence) { if (bareAddress == null) return; Presence presence = (Presence) packet; if (presence.getType() == Presence.Type.subscribe) { // Subscription request HashSet<String> set = requestedSubscriptions.get(account); if (set != null && set.contains(bareAddress)) { try { acceptSubscription(account, bareAddress); } catch (NetworkException e) { } subscriptionRequestProvider.remove(account, bareAddress); } else { subscriptionRequestProvider.add(new SubscriptionRequest( account, bareAddress), null); } return; } String verbose = StringUtils.parseResource(presence.getFrom()); String resource = Jid.getResource(presence.getFrom()); ResourceContainer resourceContainer = presenceContainers.get( account, bareAddress); ResourceItem resourceItem; if (resourceContainer == null) resourceItem = null; else resourceItem = resourceContainer.get(resource); StatusMode previousStatusMode = getStatusMode(account, bareAddress); String previousStatusText = getStatusText(account, bareAddress); if (presence.getType() == Type.available) { StatusMode statusMode = StatusMode.createStatusMode(presence); String statusText = presence.getStatus(); int priority = presence.getPriority(); if (statusText == null) statusText = ""; if (priority == Integer.MIN_VALUE) priority = 0; if (resourceItem == null) { if (resourceContainer == null) { resourceContainer = new ResourceContainer(); presenceContainers.put(account, bareAddress, resourceContainer); } resourceContainer.put(resource, new ResourceItem(verbose, statusMode, statusText, priority)); resourceContainer.updateBest(); } else { resourceItem.setVerbose(verbose); resourceItem.setStatusMode(statusMode); resourceItem.setStatusText(statusText); resourceItem.setPriority(priority); resourceContainer.updateBest(); } } else if (presence.getType() == Presence.Type.error || presence.getType() == Type.unavailable) { if (presence.getType() == Presence.Type.error && "".equals(resource) && resourceContainer != null) presenceContainers.remove(account, bareAddress); else if (resourceItem != null) { resourceContainer.remove(resource); resourceContainer.updateBest(); } } // Notify about changes StatusMode newStatusMode = getStatusMode(account, bareAddress); String newStatusText = getStatusText(account, bareAddress); if (previousStatusMode != newStatusMode || !previousStatusText.equals(newStatusText)) for (OnStatusChangeListener listener : Application .getInstance() .getManagers(OnStatusChangeListener.class)) if (previousStatusMode == newStatusMode) listener.onStatusChanged(account, bareAddress, resource, newStatusText); else listener.onStatusChanged(account, bareAddress, resource, newStatusMode, newStatusText); RosterContact rosterContact = RosterManager.getInstance() .getRosterContact(account, bareAddress); if (rosterContact != null) { ArrayList<RosterContact> rosterContacts = new ArrayList<RosterContact>(); rosterContacts.add(rosterContact); for (OnRosterChangedListener listener : Application .getInstance().getManagers( OnRosterChangedListener.class)) listener.onPresenceChanged(rosterContacts); } RosterManager.getInstance().onContactChanged(account, bareAddress); } else if (packet instanceof RosterPacket && ((RosterPacket) packet).getType() != IQ.Type.ERROR) { RosterPacket rosterPacket = (RosterPacket) packet; for (RosterPacket.Item item : rosterPacket.getRosterItems()) { if (item.getItemType() == ItemType.both || item.getItemType() == ItemType.from) { String user = Jid.getBareAddress(item.getUser()); if (user == null) continue; // Contact can be subscribed or unsubscribed from // another IM. subscriptionRequestProvider.remove(account, user); } } } } @Override public void onArchiveModificationsReceived(ConnectionItem connection) { if (!(connection instanceof AccountItem)) return; // Send presence information only when server side archive modifications // received. String account = ((AccountItem) connection).getAccount(); readyAccounts.add(account); Collection<String> previous = new HashSet<String>(); for (NestedMap.Entry<ResourceContainer> entry : presenceContainers) previous.add(entry.getSecond()); presenceContainers.clear(account); ArrayList<RosterContact> rosterContacts = new ArrayList<RosterContact>(); for (String bareAddress : previous) { RosterContact rosterContact = RosterManager.getInstance() .getRosterContact(account, bareAddress); if (rosterContact != null) rosterContacts.add(rosterContact); } for (OnRosterChangedListener listener : Application.getInstance() .getManagers(OnRosterChangedListener.class)) listener.onPresenceChanged(rosterContacts); try { resendPresence(account); } catch (NetworkException e) { } } @Override public void onDisconnect(ConnectionItem connection) { if (!(connection instanceof AccountItem)) return; String account = ((AccountItem) connection).getAccount(); readyAccounts.remove(account); } @Override public void onAccountDisabled(AccountItem accountItem) { requestedSubscriptions.remove(accountItem.getAccount()); presenceContainers.clear(accountItem.getAccount()); } /** * Sends new presence information. * * @param account * @throws NetworkException */ public void resendPresence(String account) throws NetworkException { if (!readyAccounts.contains(account)) throw new NetworkException(R.string.NOT_CONNECTED); ConnectionManager.getInstance().sendPacket(account, AccountManager.getInstance().getAccount(account).getPresence()); } }